import socket
import network
import os
import _thread

DATA_PORT = 13333

ftpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

ftpsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

ftpsocket.bind(socket.getaddrinfo("0.0.0.0", 21)[0][4])
datasocket.bind(socket.getaddrinfo("0.0.0.0", DATA_PORT)[0][4])

ftpsocket.listen(1)
datasocket.listen(1)
datasocket.settimeout(10)

dataclient = None


def send_list_data(cwd, dataclient):
    for file in os.listdir(cwd):
        stat = os.stat(get_absolute_path(cwd, file))
        file_permissions = "drwxr-xr-x" if (stat[0] & 0o170000 == 0o040000) else "-rw-r--r--"
        file_size = stat[6]
        description = "{}    1 owner group {:>13} Jan 1  1980 {}\r\n".format(
            file_permissions, file_size, file
        )
        dataclient.sendall(description)


def send_file_data(path, dataclient):
    with open(path) as file:
        chunk = file.read(128)
        while len(chunk) > 0:
            dataclient.sendall(chunk)
            chunk = file.read(128)


def save_file_data(path, dataclient):
    with open(path, "w") as file:
        chunk = dataclient.read(128)
        while len(chunk) > 0:
            file.write(chunk)
            chunk = dataclient.read(128)


def get_absolute_path(cwd, payload):
    # if it doesn't start with / consider
    # it a relative path
    if not payload.startswith("/"):
        payload = cwd + "/" + payload
    # and don't leave any trailing /
    return payload.rstrip("/")


def ftp():
    try:
        dataclient = None
        while True:
            cwd = "/"
            cl, remote_addr = ftpsocket.accept()
            cl.settimeout(300)
            try:
                print("FTP connection from:", remote_addr)
                cl.sendall("220 Hello, this is the ESP8266.\r\n")
                while True:
                    data = cl.readline().decode("utf-8").replace("\r\n", "")
                    if len(data) <= 0:
                        print("Client is dead")
                        break

                    command, payload = (data.split(" ") + [""])[:2]
                    command = command.upper()

                    print("Command={}, Payload={}".format(command, payload))

                    if command == "USER":
                        cl.sendall("230 Logged in.\r\n")
                    elif command == "SYST":
                        cl.sendall("215 ESP8266 MicroPython\r\n")
                    elif command == "SYST":
                        cl.sendall("502\r\n")
                    elif command == "PWD":
                        cl.sendall('257 "{}"\r\n'.format(cwd))
                    elif command == "CWD":
                        path = get_absolute_path(cwd, payload)
                        try:
                            files = os.listdir(path)
                            cwd = path
                            cl.sendall('250 Directory changed successfully\r\n')
                        except:
                            cl.sendall('550 Failed to change directory\r\n')
                    elif command == "EPSV":
                        cl.sendall('502\r\n')
                    elif command == "TYPE":
                        # probably should switch between binary and not
                        cl.sendall('200 Transfer mode set\r\n')
                    elif command == "SIZE":
                        path = get_absolute_path(cwd, payload)
                        try:
                            size = os.stat(path)[6]
                            cl.sendall('213 {}\r\n'.format(size))
                        except:
                            cl.sendall('550 Could not get file size\r\n')
                    elif command == "QUIT":
                        cl.sendall('221 Bye.\r\n')
                    elif command == "PASV":
                        addr = network.WLAN().ifconfig()[0]
                        cl.sendall(
                            '227 Entering Passive Mode ({},{},{}).\r\n'.format(
                                addr.replace('.', ','), DATA_PORT >> 8, DATA_PORT % 256
                            )
                        )
                        dataclient, data_addr = datasocket.accept()
                        print("FTP Data connection from:", data_addr)
                    elif command == "LIST":
                        try:
                            send_list_data(cwd, dataclient)
                            dataclient.close()
                            cl.sendall("150 Here comes the directory listing.\r\n")
                            cl.sendall("226 Listed.\r\n")
                        except:
                            cl.sendall('550 Failed to list directory\r\n')
                        finally:
                            dataclient.close()
                    elif command == "RETR":
                        try:
                            send_file_data(get_absolute_path(cwd, payload), dataclient)
                            dataclient.close()
                            cl.sendall("150 Opening data connection.\r\n")
                            cl.sendall("226 Transfer complete.\r\n")
                        except:
                            cl.sendall('550 Failed to send file\r\n')
                        finally:
                            dataclient.close()
                    elif command == "STOR":
                        try:
                            cl.sendall("150 Ok to send data.\r\n")
                            save_file_data(get_absolute_path(cwd, payload), dataclient)
                            dataclient.close()
                            cl.sendall("226 Transfer complete.\r\n")
                        except:
                            cl.sendall('550 Failed to send file\r\n')
                        finally:
                            dataclient.close()
                    else:
                        cl.sendall("502 Unsupported command.\r\n")
                        print("Unsupported command {} with payload {}".format(command, payload))

            finally:
                cl.close()
    finally:
        datasocket.close()
        ftpsocket.close()
        if dataclient is not None:
            dataclient.close()


_thread.start_new_thread(ftp, ())
