
from ftplib import FTP,error_perm, all_errors
import posixpath
from io import BytesIO,SEEK_SET

from .source import DataSource
import sys
import re

reftp = re.compile('(ssh|ftp)\:\/\/(([^@:]+)?:?([^@]+)?@)?([^:]+)(:[0-9]+)?\/(.*)')

def parseFTPurl( url ):
    m = reftp.match( url )
    if m:
        g = m.groups()
        result = {
            "mode"     : g[0],
            "username" : g[2] or "",
            "password" : g[3] or "",
            "hostname" : g[4] or "",
            "port"     : int(g[5][1:]) if g[5] else 0,
            "path"     : g[6] or "/",
        }
        if result['port'] == 0:
            if result['mode'] == ssh:
                result['port'] = 22
            else:
                result['port'] = 21 # ftp port default
        return result

    raise ValueError("invalid: %s"%url)

def utf8_fix(s):
    return ''.join([ a if ord(a)<128 else "%02X"%ord(a) for a in s])

class FTPWriter(object):
    """docstring for FTPWriter"""
    def __init__(self, ftp, path):
        super(FTPWriter, self).__init__()
        self.ftp = ftp
        self.path = path
        self.file = BytesIO()

    def write(self,data):
        return self.file.write(data)

    def seek(self,pos,whence=SEEK_SET):
        return self.file.seek(pos,whence)

    def tell(self):
        return self.file.tell()

    def close(self):
        self.file.seek(0)
        text = "STOR " + utf8_fix(self.path)
        self.ftp.storbinary(text, self.file)

    def __enter__(self):
        return self

    def __exit__(self,typ,val,tb):
        if typ is None:
            self.close()

class FTPReader(object):
    """docstring for FTPWriter"""
    def __init__(self, ftp, path):
        super(FTPReader, self).__init__()
        self.ftp = ftp
        self.path = path
        self.file = BytesIO()

        # open the file
        text = "RETR " + utf8_fix(self.path)
        self.ftp.retrbinary(text, self.file.write)
        self.file.seek(0)

    def read(self,n=None):
        return self.file.read(n)

    def seek(self,pos,whence=SEEK_SET):
        return self.file.seek(pos,whence)

    def tell(self):
        return self.file.tell()

    def close(self):
        self.file.close()

    def __enter__(self):
        return self

    def __exit__(self,typ,val,tb):
        if typ is None:
            self.close()

class FTPSource(DataSource):
    """
    there is some sort of problem with utf-8/latin-1 and ftplib

    storbinary must accepts a STRING, since it builds a cmd and add
    the CRLF to the input argument using the plus operator.

    the command fails when given unicode text (ord > 127) and also
    fails whenm given a byte string.
    """

    # TODO: turn this into a directory generator
    # which first loads the directory, then loops over
    # loaded items.

    # TODO: on windows we need a way to view available
    # drive letters
    def __init__(self, host, port, username="", password=""):
        super(FTPSource, self).__init__()

        self.ftp = FTP()
        self.ftp.connect(host,port)
        self.ftp.login(username,password)

        self.hostname = "%s:%d"%(host,port)

    def root(self):
        return "/"

    def close(self):

        try:
            self.ftp.quit()
        except all_errors as e:
            sys.stderr.write("Error Closing FTP connection\n")
            sys.stderr.write("%s\n"%e)

        super().close()

    def fix(self, path):
        return utf8_fix(path)

    def join(self,*args):
        return posixpath.join(*args)

    def breakpath(self,path):
        return [ x for x in path.replace("/","\\").split("\\") if x ]

    def relpath(self,path,base):
        return posixpath.relpath(path,base)

    def normpath(self,path,root=None):
        if root and not path.startswith("/"):
            path = posixpath.join(root,path)
        return posixpath.normpath( path )

    def listdir(self,path):
        return self.ftp.nlst(path)

    def parent(self,path):
        # TODO: if path is C:\\ return empty string ?
        # empty string returns drives
        p,_ = posixpath.split(path)
        return p

    def move(self,oldpath,newpath):
        self.ftp.rename(oldpath,newpath)

    def delete(self,path):
        # todo support removing directory rmdir()
        path = utf8_fix(path)
        if self.exists( path ):
            if self.isdir(path):
                try:
                    self.ftp.rmd(path)
                except Exception as e:
                    print("ftp delete error: %s"%e)
            else:
                try:
                    self.ftp.delete(path)
                except Exception as e:
                    print("ftp delete error: %s"%e)

    def open(self,path,mode):
        if mode=="wb":
            return FTPWriter(self.ftp,path)
        elif mode=="rb":
            return FTPReader(self.ftp,path)
        raise NotImplementedError(mode)

    def exists(self,path):
        path = utf8_fix(path)
        p,n=posixpath.split(path)
        lst = set(self.listdir(p))
        return n in lst

    def isdir(self,path):
        path = utf8_fix(path)
        try:
            return self.ftp.size(path) is None
        except error_perm:
            # TODO: to think about more later,
            # under my use-case, I'm only asking if a path is a directory
            # if I Already think it exists. Under the current FTP impl
            # ftp.size() fails for various reasons unless the file exists
            # and is an accessable file. I can infer that a failure to
            # determine the size means that the path is a directory,
            # but this does not hold true under other use cases.
            # I can't cache listdir calls, but if I could, then I could
            # use that to determine if the file exists
            return True#self.exists( path )

    def mkdir(self,path):
        # this is a really ugly quick and dirty solution
        path = utf8_fix(path)
        if not self.exists(path):
            p = self.parent( path )
            try:
                if not self.exists(p):
                    self.ftp.mkd( p )
                self.ftp.mkd(path)
            except Exception as e:
                print("ftp mkd error: %s"%e)


    def split(self,path):
        return posixpath.split(path)

    def splitext(self,path):
        return posixpath.splitext(path)

    def stat(self,path):
        try:
            size = self.ftp.size(path)
        except error_perm:
            size = None

        result = {
            "isDir" : size is None,
            "isLink": False,
            "mtime" : 0,
            "ctime" : 0,
            "size"  : size or 0,
            "name"  : self.split(path)[1],
            "mode"  : 0
        }
        return result

    def stat_fast(self,path):
        # not fast for thus file system :(
        try:
            size = self.ftp.size(path)
        except error_perm:
            size = None

        result = {
            "name"  : self.split(path)[1],
            "size"  : size or 0,
            "isDir" : size is None,
            "isLink" : False,
        }
        return result

    def chmod(self,path,mode):
        print("chmod not implemented")

    def getExportPath(self,path):
        return self.hostname+path

[((10, 8, 10, 83), 're.compile', 're.compile', ({(10, 19, 10, 82): '"""(ssh|ftp)\\\\:\\\\/\\\\/(([^@:]+)?:?([^@]+)?@)?([^:]+)(:[0-9]+)?\\\\/(.*)"""'}, {}), "('(ssh|ftp)\\\\:\\\\/\\\\/(([^@:]+)?:?([^@]+)?@)?([^:]+)(:[0-9]+)?\\\\/(.*)')", False, 'import re\n'), ((42, 20, 42, 29), 'io.BytesIO', 'BytesIO', ({}, {}), '()', False, 'from io import BytesIO, SEEK_SET\n'), ((71, 20, 71, 29), 'io.BytesIO', 'BytesIO', ({}, {}), '()', False, 'from io import BytesIO, SEEK_SET\n'), ((117, 19, 117, 24), 'ftplib.FTP', 'FTP', ({}, {}), '()', False, 'from ftplib import FTP, error_perm, all_errors\n'), ((140, 15, 140, 36), 'posixpath.join', 'posixpath.join', ({(140, 30, 140, 35): '*args'}, {}), '(*args)', False, 'import posixpath\n'), ((146, 15, 146, 43), 'posixpath.relpath', 'posixpath.relpath', ({(146, 33, 146, 37): 'path', (146, 38, 146, 42): 'base'}, {}), '(path, base)', False, 'import posixpath\n'), ((151, 15, 151, 41), 'posixpath.normpath', 'posixpath.normpath', ({(151, 35, 151, 39): 'path'}, {}), '(path)', False, 'import posixpath\n'), ((159, 14, 159, 35), 'posixpath.split', 'posixpath.split', ({(159, 30, 159, 34): 'path'}, {}), '(path)', False, 'import posixpath\n'), ((189, 12, 189, 33), 'posixpath.split', 'posixpath.split', ({(189, 28, 189, 32): 'path'}, {}), '(path)', False, 'import posixpath\n'), ((223, 15, 223, 36), 'posixpath.split', 'posixpath.split', ({(223, 31, 223, 35): 'path'}, {}), '(path)', False, 'import posixpath\n'), ((226, 15, 226, 39), 'posixpath.splitext', 'posixpath.splitext', ({(226, 34, 226, 38): 'path'}, {}), '(path)', False, 'import posixpath\n'), ((150, 19, 150, 44), 'posixpath.join', 'posixpath.join', ({(150, 34, 150, 38): 'root', (150, 39, 150, 43): 'path'}, {}), '(root, path)', False, 'import posixpath\n'), ((131, 12, 131, 62), 'sys.stderr.write', 'sys.stderr.write', ({(131, 29, 131, 61): '"""Error Closing FTP connection\n"""'}, {}), "('Error Closing FTP connection\\n')", False, 'import sys\n'), ((132, 12, 132, 38), 'sys.stderr.write', 'sys.stderr.write', ({(132, 29, 132, 37): "('%s\\n' % e)"}, {}), "('%s\\n' % e)", False, 'import sys\n')]