from ftplib import FTP_TLS as FTP
import io
import json
import tempfile
import subprocess
import time
import os
import datetime
import zipfile
from customtkinter import *
import time
import math
import tarfile
from pathlib import Path
import shutil



def get_tar_members_stripped(tar, n_folders_stripped = 1):
    members = []
    for member in tar.getmembers():
        p = Path(member.path)
        member.path = p.relative_to(*p.parts[:n_folders_stripped])
        members.append(member)
    return members

def unzip(zip, dest):
    for file_info in zip.infolist():
        if file_info.is_dir():
            continue

        file_path = file_info.filename

        extracted_path = file_path.split("/", 1)[1]
        extracted_path = os.path.join(dest, extracted_path)
        print(extracted_path)
        os.makedirs(os.path.dirname(extracted_path), exist_ok=True)
        with open(extracted_path, 'wb') as dst:
            with zip.open(file_info, 'r') as src:
                shutil.copyfileobj(src, dst)


def install_java(mc_version):
    global data_dir
    tokens = mc_version.split(".")
    java = 8
    if tokens[1] == "17":
        java = 17
    elif tokens[1] == "18" or tokens[1] == "19":
        java = 17
    elif int(tokens[1]) > 19:
        if tokens[1] == "20" and int(tokens[2]) < 5:
            java = 17
        else:
            java = 21

    suff = "win"
    ext = ".zip"
    if os.name == "posix":
        suff = "lin"
        ext = ".tar.gz"

    java_path = "java/java-"+str(java)+"-"+suff+ext
    if not os.path.exists(data_dir+"/java/java-"+str(java)+"-"+suff):
        print("Downloading Java "+str(java))
        ftp = FTP("gitea.piwalker.net")
        ftp.login()
        ftp.prot_p()
        with io.BytesIO() as buff:
            ftp.retrbinary("RETR "+java_path,buff.write)
            if os.name == "posix":
                buff.seek(0)
                with tarfile.open(fileobj=buff) as tar:
                    print(tar.getnames())
                    tar.extractall(data_dir+"/java/java-"+str(java)+"-"+suff, members=get_tar_members_stripped(tar, 1))
            else:
                with zipfile.ZipFile(buff) as zip:
                    unzip(zip, data_dir+"/java/java-"+str(java)+"-"+suff)


    return java
            


def resource_path(relative_path):
    try:
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

def perform_installation(instance_name, prism_command, prism_instance_path, pack):
    global data_dir
    if os.name == "posix":
        data_dir = os.getenv("HOME") + "/.local/share/FCLauncher"
    else:
        data_dir = os.getenv("APPDATA") + "/FCLauncher"

    Path(data_dir).mkdir(parents=True, exist_ok=True)
    Path(data_dir+"/java").mkdir(parents=True, exist_ok=True)
 
    try:
        ftp = FTP("gitea.piwalker.net")
        ftp.login()
        ftp.prot_p()
        ftp.cwd(pack)
    
        # Fetching versions.json from FTP
        bio = io.BytesIO()
        ftp.retrbinary("RETR versions.json", bio.write)
        bio.seek(0)
        versions = json.load(bio)
        bio.close()
    
        # Checking current version
        version = "0.0.0"
        version_file_path = os.path.join(prism_instance_path, instance_name, ".minecraft", "version.txt")
        if os.path.exists(version_file_path):
            with open(version_file_path, 'r') as fp:
                version = fp.readline().rstrip()
    
        # Checking if update is needed
        if version != versions[-1]["Version"]:
            print(f"Current version: {version}")
            print(f"Latest version: {versions[-1]['Version']}")
        
            # Downloading modpack
            with tempfile.TemporaryDirectory() as temp_dir:
                modpack_file_path = os.path.join(temp_dir, instance_name + ".mrpack")
                with open(modpack_file_path, 'wb') as modpack:
                    ftpDownload(ftp, versions[-1]["File"], modpack)
                
                # Running PrismLauncher with modpack
                subprocess.Popen([prism_command, '-I', modpack_file_path])
            
                # Waiting for installation to complete
                while True:
                    time.sleep(5)
                    if os.path.exists(version_file_path):
                        with open(version_file_path, 'r') as fp:
                            version = fp.readline().rstrip()
                    if version == versions[-1]["Version"]:
                        info_file_path = os.path.join(prism_instance_path, instance_name, "mmc-pack.json")
                        mc_version = ""
                        with open(info_file_path) as file:
                            info = json.load(file)
                            for component in info["components"]:
                                if component["uid"] == "net.minecraft":
                                    mc_version = component["version"]
                                    break;
                        java = install_java(mc_version)
                        option_path = os.path.join(prism_instance_path, instance_name, "instance.cfg")
                        data = []
                        suff = "win"
                        cmd = "java.exe"
                        if os.name == "posix":
                            suff = "lin"
                            cmd = "java"
                        with open(option_path, 'r') as file:
                           data = file.readlines()
                        was_set = False
                        for ind, line in enumerate(data):
                            if line.startswith("JavaPath="):
                                data[ind] = "JavaPath="+data_dir.replace("\\", "/")+"/java/java-"+str(java)+"-"+suff+"/bin/"+cmd+"\n"
                                was_set = True
                        if not was_set:
                            data.append("JavaPath="+data_dir.replace("\\", "/")+"/java/java-"+str(java)+"-"+suff+"/bin/"+cmd+"\n")
                            data.append("OverrideJavaLocation=true\n")
                            data.append("OverrideJava=true\n")
                        with open(option_path, 'w') as file:
                            file.writelines(data)
                        print(data)


                        subprocess.run([prism_command, '-l', instance_name])
                        time.sleep(30)
                        break
        else:
            subprocess.run([prism_command, '-l', instance_name])
    except Exception as e:
        print(e)
        print("Unable to check for updates. Modpack may be out of date")
        subprocess.run([prism_command, '-l', instance_name])


def upload_pack(username, password, version_tag, fileName, pack):
    ftp = FTP("gitea.piwalker.net", username, password)
    ftp.prot_p()
    ftp.cwd(pack)
    bio = io.BytesIO()
    ftp.retrbinary("RETR versions.json", bio.write)
    bio.seek(0)
    versions = json.load(bio)
    bio.close()
    time = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
    versions.append({"Version":version_tag, "Date":time, "File":"Versions/"+pack+time+".mrpack"})
    with zipfile.ZipFile(fileName, 'r') as zin:
        zipbytes = io.BytesIO()
        zout = zipfile.ZipFile(zipbytes, 'w')
        for item in zin.infolist():
            buffer = zin.read(item.filename)
            if item.filename != "overrides/version.txt":
                zout.writestr(item, buffer)
        zout.writestr("overrides/version.txt", version_tag)
        zout.close()
        zipbytes.seek(0)
        ftp.storbinary("STOR "+versions[len(versions)-1]["File"], zipbytes)
        bio = io.BytesIO()
        bio.write(json.dumps(versions).encode())
        bio.seek(0)
        ftp.storbinary("STOR versions.json", bio)
        bio.close()
        modpackUpdate(pack, ftp)
    ftp.close()

def getModpacks():
    ftp = FTP("gitea.piwalker.net")
    ftp.login()
    ftp.prot_p()
    bio = io.BytesIO()
    ftp.retrbinary("RETR modpacks.json", bio.write)
    bio.seek(0)
    ftp.close()
    return json.load(bio)

def uploadModpacks(modpacks, ftp):
    ftp.cwd("/ftp");
    bio = io.BytesIO()
    bio.write(json.dumps(modpacks).encode())
    bio.seek(0)
    ftp.storbinary("STOR modpacks.json", bio)
    bio.close()

def modpackUpdate(id, ftp):
    modpacks = getModpacks()
    time = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
    for modpack in modpacks:
        if modpack["id"] == id:
            modpack["last-updated"] = time
    uploadModpacks(modpacks, ftp);

def createModpack(id, name, username, password):
    modpacks = getModpacks()
    time = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
    modpacks.append({"id":id, "name":name, "last-updated":time})
    ftp = FTP("gitea.piwalker.net", username, password)
    ftp.prot_p()
    uploadModpacks(modpacks,ftp)
    ftp.mkd(id)
    ftp.cwd(id)
    versions = []
    bio = io.BytesIO()
    bio.write(json.dumps(versions).encode())
    bio.seek(0)
    ftp.storbinary("STOR versions.json", bio)
    ftp.mkd("Versions")
    ftp.close()

def deleteModpack(username, password, id):
    ftp = FTP("gitea.piwalker.net", username, password)
    ftp.prot_p()
    modpacks = getModpacks()
    for pack in modpacks:
        if pack["id"] == id:
            modpacks.remove(pack)
            break
    uploadModpacks(modpacks, ftp)
    deleteFolder(ftp, id)

def deleteFolder(ftp, path):
    print("Deleting folder: "+path)
    ftp.cwd(path)
    for item in ftp.nlst():
        try:
            print("deleting file: "+path)
            ftp.delete(item)
        except:
            deleteFolder(ftp, item)
    ftp.cwd("..")
    ftp.rmd(path)

def ftpDownload(ftp, file, stream):
    #create tkinter window
    print("downloading file: "+file)
    dialog = CTk()
    if os.name == 'posix':
        dialog.attributes('-type', 'dialog')
    dialog.title("Downloading Modpack")
    set_appearance_mode("dark")
    set_default_color_theme("blue")

    global pbar
    pbar = CTkProgressBar(master=dialog)
    pbar.pack(padx=20, pady=20)
    global progress
    global ETA
    progress = StringVar()
    ETA = StringVar()
    progress_label = CTkLabel(master=dialog, textvariable=progress)
    progress_label.pack()
    eta_label = CTkLabel(master=dialog, textvariable=ETA)
    eta_label.pack()
    size = ftp.size(file)
    global total
    total = 0
    global start
    global timer
    timer = 0
    start = time.time()
    dialog.update()

    def downloadCallback(data):
        global total
        global pbar
        global start
        global progress
        global ETA
        global timer
        stream.write(data)
        total += len(data)
        if time.time() - timer >= 1:
            progress.set(str(round(total/1048576, 1))+" MB / "+str(round(size/1048576, 1))+" MB @ " + str(round((total/1048576)/(time.time()-start), 3))+" MB/s")
            time_left = (size-total)/(total/(time.time()-start))
            ETA.set("ETA: " + str(datetime.timedelta(seconds=math.ceil(time_left))))
            pbar.set(total/size)
            timer = time.time()
        dialog.update()
    ftp.retrbinary("RETR " + file, downloadCallback)
    dialog.destroy()

