#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import ftplib
import json
import os
import pathlib
import socket
import sys
from copy import deepcopy
from random import randint
from shutil import rmtree
from threading import Thread
from time import sleep
from tkinter import Button, Entry, Label, StringVar, Tk

from PIL import Image, ImageTk
from requests import post

# import rethinkdb as r
from compare_json import compare_json
from dir_to_json import get_json

# from tcping import Ping

#ORIGINAL = "/home/kirbylife/Proyectos/munyal_test/another"
ORIGINAL = "/home/kirbylife/Proyectos/munyal_test/"
IP = "localhost"
USERNAME = "munyal"
PASSWORD = "123"
HOSTNAME = socket.gethostname() + str(randint(1, 100000000))
SKIP_UPLOAD = []
FLAG_DOWNLOAD = False
pending_routes = []


def check_network(port):
    ping = Ping(IP, port, 20)
    ping.ping(1)
    print(SKIP_UPLOAD)
    return ping.result.rows[0].successed == 1


def watch_dir():
    global pending_routes
    folder = os.path.join(os.getenv("HOME"), ".munyal")
    if not os.path.exists(folder):
        pathlib.Path(folder).mkdir(parents=True)

    actual_file = os.path.join(folder, "actual.json")
    if not os.path.exists(actual_file):
        with open(actual_file, "w") as f:
            f.write(json.dumps([]))
    pending_file = os.path.join(folder, "pending_routes.json")
    if not os.path.exists(pending_file):
        with open(pending_file, "w") as f:
            f.write(json.dumps([]))

    with open(actual_file, "r") as f:
        actual = json.loads(f.read())
    actual = get_json(ORIGINAL)
    new = deepcopy(actual)
    with open(pending_file, "r") as f:
        pending_routes = json.loads(f.read())
    new = get_json(ORIGINAL)
    while True:
        sleep(0.2)
        while True:
            try:
                jsons = compare_json(deepcopy(actual), deepcopy(new))
            except:
                new = get_json(ORIGINAL)
            else:
                break
        changes = get_changes(jsons)

        pending_routes = pending_routes + changes
        with open(pending_file, "w") as f:
            f.write(json.dumps(pending_routes, indent=4))

        actual = deepcopy(new)
        with open(actual_file, "w") as f:
            f.write(json.dumps(actual, indent=4))
        while True:
            try:
                new = get_json(ORIGINAL)
            except:
                pass
            else:
                break


def need_deleted(items, route):
    out = []
    for item in items:
        if item.get("is_file"):
            out.append({
                "action": "delete",
                "route": os.path.join(route, item.get('name'))
            })
        else:
            if item.get('content'):
                out = out + need_deleted(item.get("content"),
                                         os.path.join(route, item.get('name')))
            else:
                out.append({
                    "action": "delete_folder",
                    "route": os.path.join(route, item.get('name'))
                })
    return out


def need_added(items, route):
    out = []
    for item in items:
        if item.get("is_file"):
            out.append({
                "action": "add",
                "route": os.path.join(route, item.get('name'))
            })
        else:
            if item.get('content'):
                out = out + need_added(item.get("content"),
                                       os.path.join(route, item.get('name')))
            else:
                out.append({
                    "action": "add_folder",
                    "route": os.path.join(route, item.get('name'))
                })
    return out


def get_changes(jsons, route=''):
    delete, add = jsons
    out = need_deleted(delete, route) + need_added(add, route)
    return out


def _is_ftp_dir(ftp_handle, name):
    original_cwd = ftp_handle.pwd()
    try:
        ftp_handle.cwd(name)
        ftp_handle.cwd(original_cwd)
        return True
    except:
        return False


def _make_parent_dir(fpath):
    #dirname = os.path.dirname(fpath)
    dirname = os.path.join(ORIGINAL, fpath)
    while not os.path.exists(dirname):
        try:
            os.makedirs(dirname)
            print("created {0}".format(dirname))
        except:
            _make_parent_dir(dirname)


def _download_ftp_file(ftp_handle, name, dest, overwrite):
    if not os.path.exists(dest) or overwrite is True:
        try:
            with open(dest, 'wb') as f:
                ftp_handle.retrbinary("RETR {0}".format(name), f.write)
            print("downloaded: {0}".format(dest))
        except FileNotFoundError:
            print("FAILED: {0}".format(dest))
    else:
        print("already exists: {0}".format(dest))


def _mirror_ftp_dir(ftp_handle, name, overwrite):
    for item in ftp_handle.nlst(name):
        SKIP_UPLOAD.append(item)
        if _is_ftp_dir(ftp_handle, item):
            _make_parent_dir(item.lstrip("/"))
            _mirror_ftp_dir(ftp_handle, os.path.join(name, item), overwrite)
        else:
            _download_ftp_file(ftp_handle, item, os.path.join(ORIGINAL, item),
                               overwrite)


def download_ftp_tree(overwrite=False):
    FLAG_DOWNLOAD = True
    ftp_handle = ftplib.FTP(IP, USERNAME, PASSWORD)
    path = ""
    original_directory = os.getcwd()
    os.chdir(ORIGINAL)
    _mirror_ftp_dir(ftp_handle, path, overwrite)
    os.chdir(original_directory)
    ftp_handle.close()
    FLAG_DOWNLOAD = False


def upload(*args):
    global SKIP_UPLOAD
    global pending_routes
    print("Modulo de subida listo")
    while True:
        sleep(0.1)
        if check_network('21') and pending_routes:
            change = pending_routes.pop(0)
            if change['route'] not in SKIP_UPLOAD:
                ftp = ftplib.FTP(IP, USERNAME, PASSWORD)
                route = os.path.join(ORIGINAL, change['route'])
                success = False
                while not success:
                    try:
                        if change['action'] == 'add':
                            while FLAG_DOWNLOAD:
                                print("Wait")
                            print("Agregar archivo")
                            with open(route, "rb") as f:
                                ftp.storbinary("STOR /" + change['route'], f)
                        elif change['action'] == 'add_folder':
                            print("Agregar carpeta")
                            ftp.mkd(change['route'])
                        elif change['action'] == 'delete':
                            print("Borrar archivo")
                            ftp.delete(change['route'])
                        elif change['action'] == 'delete_folder':
                            print("Borrar carpeta")
                            ftp.rmd(change['route'])
                        else:
                            print("Unexpected action")
                    except:
                        print("Error uploading\n")
                    r = post("http://" + IP + ':8000/upload',
                             data={
                                 'host': HOSTNAME,
                                 'action': change['action'],
                                 'route': change['route']
                             })
                    r = json.loads(r.text)
                    print(json.dumps(r, indent=4))
                    success = r['status'] == 'ok'
                ftp.close()
            else:
                SKIP_UPLOAD.pop()
    return 0


def download(*args):
    global SKIP_UPLOAD
    while True:
        sleep(1)
        if check_network(28015) and check_network(21):
            try:
                download_ftp_tree(overwrite=False)

                print("Modulo de descarga listo")
                print("Carpeta " + ORIGINAL)
                r.connect(IP, 28015).repl()
                cursor = r.table("changes").changes().run()
                for document in cursor:
                    change = document['new_val']
                    #print(change)
                    if change['host'] != HOSTNAME:
                        route = os.path.join(ORIGINAL, change['route'])
                        SKIP_UPLOAD.append(change['route'])
                        try:
                            if change['action'] == 'add':
                                print("Agregar archivo")
                                FLAG_DOWNLOAD = True
                                ftp = ftplib.FTP(IP, USERNAME, PASSWORD)
                                with open(route, "wb") as f:
                                    ftp.retrbinary("RETR /" + change['route'],
                                                   f.write)
                                ftp.close()
                                FLAG_DOWNLOAD = False
                            elif change['action'] == 'add_folder':
                                print("Agregar carpeta")
                                pathlib.Path(route).mkdir(parents=True)
                            elif change['action'] == 'delete':
                                print("Borrar archivo")
                                pathlib.Path(route).unlink()
                            elif change['action'] == 'delete_folder':
                                print("Borrar carpeta")
                                rmtree(route)
                            else:
                                print("Unexpected action")
                        except OSError as e:
                            print("Error en el sistema operativo")
                        except:
                            print("Error en el servidor FTP")
            except r.errors.ReqlDriverError as e:
                print("Conection refused with rethinkdb")
    return 0


def run_client(window, password, username, host, folder):
    global PASSWORD
    global IP
    global USERNAME
    global ORIGINAL

    PASSWORD = password.get()
    USERNAME = username.get()
    IP = host.get()
    ORIGINAL = folder.get()

    if not os.path.exists(ORIGINAL):
        pathlib.Path(ORIGINAL).mkdir(parents=True)

    download_thread = Thread(target=download, args=[window])
    download_thread.setDaemon(True)
    download_thread.start()

    upload_thread = Thread(target=upload, args=[window])
    upload_thread.setDaemon(True)
    upload_thread.start()

    watch_dir_thread = Thread(target=watch_dir)
    watch_dir_thread.setDaemon(True)
    watch_dir_thread.start()


def main(args):
    root = Tk()
    root.geometry("250x400")

    img_logo = Image.open("img/logo.png")
    img_tk = ImageTk.PhotoImage(img_logo.resize((200, 150), Image.ANTIALIAS))

    host = StringVar()
    host.set("localhost")
    host_field = Entry(root, textvariable=host)

    passwd = StringVar()
    passwd_field = Entry(root, textvariable=passwd, show="*")

    folder = StringVar()
    folder.set(os.path.join(os.getenv("HOME"), "Munyal"))
    folder_field = Entry(root, textvariable=folder)

    connect = Button(root,
                     text="Conectar",
                     command=lambda: run_client(root, passwd, host, folder))

    Label(root, text="MUNYAL").pack()
    Label(root, image=img_tk).pack()
    Label(root, text="").pack()
    Label(root, text="Ruta del servidor").pack()
    host_field.pack()
    Label(root, text="").pack()
    Label(root, text="ContraseƱa").pack()
    passwd_field.pack()
    Label(root, text="").pack()
    Label(root, text="Carpeta a sincronizar").pack()
    folder_field.pack()
    Label(root, text="").pack()
    connect.pack()

    root.mainloop()


if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))