"""
A helper module that can work with paths
that can refer to data inside a zipfile
"""
import errno as _errno 
import os as _os 
import stat as _stat 
import sys as _sys 
import time as _time 
import zipfile as _zipfile 

_DFLT_DIR_MODE =(
_stat .S_IXOTH 
|_stat .S_IXGRP 
|_stat .S_IXUSR 
|_stat .S_IROTH 
|_stat .S_IRGRP 
|_stat .S_IRUSR 
)

_DFLT_FILE_MODE =_stat .S_IROTH |_stat .S_IRGRP |_stat .S_IRUSR 


if _sys .version_info [0 ]==2 :
    from StringIO import StringIO as _BaseBytesIO 
    from StringIO import StringIO as _BaseStringIO 

    class _StringIO (_BaseStringIO ):
        def __enter__ (self ):
            return self 

        def __exit__ (self ,exc_type ,exc_value ,traceback ):
            self .close ()
            return False 

    class _BytesIO (_BaseBytesIO ):
        def __enter__ (self ):
            return self 

        def __exit__ (self ,exc_type ,exc_value ,traceback ):
            self .close ()
            return False 

else :
    from io import BytesIO as _BytesIO 
    from io import StringIO as _StringIO 


def _locate (path ):
    full_path =path 
    if _os .path .exists (path ):
        return path ,None 

    else :
        rest =[]
        root =_os .path .splitdrive (path )
        while path and path !=root :
            path ,bn =_os .path .split (path )
            rest .append (bn )
            if _os .path .exists (path ):
                break 

        if path ==root :
            raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")

        if not _os .path .isfile (path ):
            raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")

        rest .reverse ()
        return path ,"/".join (rest ).strip ("/")


_open =open 


def open (path ,mode ="r"):
    if "w"in mode or "a"in mode :
        raise IOError (_errno .EINVAL ,path ,"Write access not supported")
    elif "r+"in mode :
        raise IOError (_errno .EINVAL ,path ,"Write access not supported")

    full_path =path 
    path ,rest =_locate (path )
    if not rest :
        return _open (path ,mode )

    else :
        try :
            zf =_zipfile .ZipFile (path ,"r")

        except _zipfile .error :
            raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")

        try :
            data =zf .read (rest )
        except (_zipfile .error ,KeyError ):
            zf .close ()
            raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")
        zf .close ()

        if mode =="rb":
            return _BytesIO (data )

        else :
            if _sys .version_info [0 ]==3 :
                data =data .decode ("ascii")

            return _StringIO (data )


def listdir (path ):
    full_path =path 
    path ,rest =_locate (path )
    if not rest and not _os .path .isfile (path ):
        return _os .listdir (path )

    else :
        try :
            zf =_zipfile .ZipFile (path ,"r")

        except _zipfile .error :
            raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")

        result =set ()
        seen =False 
        try :
            for nm in zf .namelist ():
                if rest is None :
                    seen =True 
                    value =nm .split ("/")[0 ]
                    if value :
                        result .add (value )

                elif nm .startswith (rest ):
                    if nm ==rest :
                        seen =True 
                        value =""
                        pass 
                    elif nm [len (rest )]=="/":
                        seen =True 
                        value =nm [len (rest )+1 :].split ("/")[0 ]
                    else :
                        value =None 

                    if value :
                        result .add (value )
        except _zipfile .error :
            zf .close ()
            raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")

        zf .close ()

        if not seen :
            raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")

        return list (result )


def isfile (path ):
    full_path =path 
    path ,rest =_locate (path )
    if not rest :
        ok =_os .path .isfile (path )
        if ok :
            try :
                zf =_zipfile .ZipFile (path ,"r")
                return False 
            except (_zipfile .error ,IOError ):
                return True 
        return False 

    zf =None 
    try :
        zf =_zipfile .ZipFile (path ,"r")
        zf .getinfo (rest )
        zf .close ()
        return True 
    except (KeyError ,_zipfile .error ):
        if zf is not None :
            zf .close ()


        try :
            zf .getinfo (rest +"/")
        except KeyError :
            pass 
        else :
            return False 

        rest =rest +"/"
        for nm in zf .namelist ():
            if nm .startswith (rest ):

                return False 


        raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")


def isdir (path ):
    full_path =path 
    path ,rest =_locate (path )
    if not rest :
        ok =_os .path .isdir (path )
        if not ok :
            try :
                zf =_zipfile .ZipFile (path ,"r")
            except (_zipfile .error ,IOError ):
                return False 
            return True 
        return True 

    zf =None 
    try :
        try :
            zf =_zipfile .ZipFile (path )
        except _zipfile .error :
            raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")

        try :
            zf .getinfo (rest )
        except KeyError :
            pass 
        else :

            return False 

        rest =rest +"/"
        try :
            zf .getinfo (rest )
        except KeyError :
            pass 
        else :

            return True 

        for nm in zf .namelist ():
            if nm .startswith (rest ):
                return True 

        raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")
    finally :
        if zf is not None :
            zf .close ()


def islink (path ):
    full_path =path 
    path ,rest =_locate (path )
    if not rest :
        return _os .path .islink (path )

    try :
        zf =_zipfile .ZipFile (path )
    except _zipfile .error :
        raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")
    try :
        try :
            zf .getinfo (rest )
        except KeyError :
            pass 
        else :

            return False 

        rest +="/"
        try :
            zf .getinfo (rest )
        except KeyError :
            pass 
        else :

            return False 

        for nm in zf .namelist ():
            if nm .startswith (rest ):

                return False 

        raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")

    finally :
        zf .close ()


def readlink (path ):
    full_path =path 
    path ,rest =_locate (path )
    if rest :

        raise OSError (_errno .ENOENT ,full_path ,"No such file or directory")

    return _os .readlink (path )


def getmode (path ):
    full_path =path 
    path ,rest =_locate (path )
    if not rest :
        return _stat .S_IMODE (_os .stat (path ).st_mode )

    zf =None 
    try :
        zf =_zipfile .ZipFile (path )
        info =None 

        try :
            info =zf .getinfo (rest )
        except KeyError :
            pass 

        if info is None :
            try :
                info =zf .getinfo (rest +"/")
            except KeyError :
                pass 

        if info is None :
            rest =rest +"/"
            for nm in zf .namelist ():
                if nm .startswith (rest ):
                    break 
            else :
                raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")


            return _DFLT_DIR_MODE 


        if (info .external_attr >>16 )!=0 :
            return _stat .S_IMODE (info .external_attr >>16 )
        else :
            return _DFLT_FILE_MODE 

    finally :
        if zf is not None :
            zf .close ()


def getmtime (path ):
    full_path =path 
    path ,rest =_locate (path )
    if not rest :
        return _os .path .getmtime (path )

    zf =None 
    try :
        zf =_zipfile .ZipFile (path )
        info =None 

        try :
            info =zf .getinfo (rest )
        except KeyError :
            pass 

        if info is None :
            try :
                info =zf .getinfo (rest +"/")
            except KeyError :
                pass 

        if info is None :
            rest =rest +"/"
            for nm in zf .namelist ():
                if nm .startswith (rest ):
                    break 
            else :
                raise IOError (_errno .ENOENT ,full_path ,"No such file or directory")




            return _os .path .getmtime (path )

        return _time .mktime (info .date_time +(0 ,0 ,-1 ))

    finally :
        if zf is not None :
            zf .close ()
