import * as THREE from 'three';
import * as fflate from 'fflate';

const debugFileHandling = false;
let loadingCallback = null;
let sceneLoadingCallback = null;

export function setLoadingCallback(cb)
{
    loadingCallback = cb;
}

export function setSceneLoadingCallback(cb)
{
    sceneLoadingCallback = cb;
}

export function dropHandler(ev)
{
    if (debugFileHandling) console.log('File(s) dropped', ev.dataTransfer.items, ev.dataTransfer.files);

    // Prevent default behavior (Prevent file from being opened)
    ev.preventDefault();

    if (ev.dataTransfer.items)
    {
        const allEntries = [];

        let haveGetAsEntry = false;
        if (ev.dataTransfer.items.length > 0)
            haveGetAsEntry =
                ("getAsEntry" in ev.dataTransfer.items[0]) ||
                ("webkitGetAsEntry" in ev.dataTransfer.items[0]);

        // Useful for debugging file handling on platforms that don't support newer file system APIs
        // haveGetAsEntry = false;

        if (haveGetAsEntry)
        {
            for (var i = 0; i < ev.dataTransfer.items.length; i++)
            {
                let item = ev.dataTransfer.items[i];
                let entry = ("getAsEntry" in item) ? item.getAsEntry() : item.webkitGetAsEntry();
                allEntries.push(entry);
            }
            handleFilesystemEntries(allEntries);
            return;
        }

        for (var i = 0; i < ev.dataTransfer.items.length; i++)
        {
            let item = ev.dataTransfer.items[i];

            // API when there's no "getAsEntry" support
            console.log(item.kind, item);
            if (item.kind === 'file')
            {
                var file = item.getAsFile();
                testAndLoadFile(file);
            }
            // could also be a directory
            else if (item.kind === 'directory')
            {
                var dirReader = item.createReader();
                dirReader.readEntries(function (entries)
                {
                    for (var i = 0; i < entries.length; i++)
                    {
                        console.log(entries[i].name);
                        var entry = entries[i];
                        if (entry.isFile)
                        {
                            entry.file(function (file)
                            {
                                testAndLoadFile(file);
                            });
                        }
                    }
                });
            }
        }
    } else
    {
        for (var i = 0; i < ev.dataTransfer.files.length; i++)
        {
            let file = ev.dataTransfer.files[i];
            testAndLoadFile(file);
        }
    }
}

export function dragOverHandler(ev)
{
    ev.preventDefault();
}

async function getBufferFromFile(fileEntry)
{

    if (fileEntry instanceof ArrayBuffer) return fileEntry;
    if (fileEntry instanceof String) return fileEntry;

    const name = fileEntry.fullPath || fileEntry.name;
    const ext = name.split('.').pop();
    const readAsText = ext === 'mtlx';

    if (debugFileHandling) console.log("reading ", fileEntry, "as text?", readAsText);

    if (debugFileHandling) console.log("getBufferFromFile", fileEntry);
    const buffer = await new Promise((resolve, reject) =>
    {
        function readFile(file)
        {
            var reader = new FileReader();
            reader.onloadend = function (e)
            {
                if (debugFileHandling) console.log("loaded", "should be text?", readAsText, this.result);
                resolve(this.result);
            };

            if (readAsText)
                reader.readAsText(file);
            else
                reader.readAsArrayBuffer(file);
        }

        if ("file" in fileEntry)
        {
            fileEntry.file(function (file)
            {
                readFile(file);
            }, (e) =>
            {
                console.error("Error reading file ", e);
            });
        }
        else
        {
            readFile(fileEntry);
        }
    });
    return buffer;
}

async function handleFilesystemEntries(entries)
{
    const allFiles = [];
    const fileIgnoreList = [
        '.gitignore',
        'README.md',
        '.DS_Store',
    ]
    const dirIgnoreList = [
        '.git',
        'node_modules',
    ]

    let isGLB = false;
    let haveMtlx = false;
    for (let entry of entries)
    {
        if (debugFileHandling) console.log("file entry", entry)
        if (entry.isFile)
        {
            if (debugFileHandling)
                console.log("single file", entry);
            if (fileIgnoreList.includes(entry.name))
            {
                continue;
            }
            allFiles.push(entry);

            if (entry.name.endsWith('glb'))
            {
                isGLB = true;
                break;
            }
        }
        else if (entry.isDirectory)
        {
            if (dirIgnoreList.includes(entry.name))
            {
                continue;
            }
            const files = await readDirectory(entry);
            if (debugFileHandling) console.log("all files", files);
            for (const file of files)
            {
                if (fileIgnoreList.includes(file.name))
                {
                    continue;
                }
                allFiles.push(file);
            }
        }
    }

    const imageLoader = new THREE.ImageLoader();

    // unpack zip files first
    for (const fileEntry of allFiles)
    {
        // special case: zip archives
        if (fileEntry.fullPath.toLowerCase().endsWith('.zip'))
        {
            await new Promise(async (resolve, reject) =>
            {
                const arrayBuffer = await getBufferFromFile(fileEntry);

                // use fflate to unpack them and add the files to the cache
                fflate.unzip(new Uint8Array(arrayBuffer), (error, unzipped) =>
                {
                    // push these files into allFiles
                    for (const [filePath, buffer] of Object.entries(unzipped))
                    {

                        // mock FileEntry for easier usage downstream
                        const blob = new Blob([buffer]);
                        const newFileEntry = {
                            fullPath: "/" + filePath,
                            name: filePath.split('/').pop(),
                            file: (callback) =>
                            {
                                callback(blob);
                            },
                            isFile: true,
                        };
                        allFiles.push(newFileEntry);
                    }

                    resolve();
                });
            });
        }
    }

    // sort so mtlx files come first
    allFiles.sort((a, b) =>
    {
        if (a.name.endsWith('.mtlx') && !b.name.endsWith('.mtlx'))
        {
            return -1;
        }
        if (!a.name.endsWith('.mtlx') && b.name.endsWith('.mtlx'))
        {
            return 1;
        }
        return 0;
    });

    if (isGLB)
    {
        console.log("Load GLB file", allFiles[0]);

        const rootFile = allFiles[0];
        THREE.Cache.add(rootFile.fullPath, await getBufferFromFile(rootFile));

        if (debugFileHandling) console.log("CACHE", THREE.Cache.files);

        sceneLoadingCallback(rootFile);
        return;
    }

    if (!allFiles[0].name.endsWith('mtlx'))
    {
        console.log("No MaterialX files dropped. Skipping content.");
        return;
    }

    if (debugFileHandling)
    {
        console.log("- All files", allFiles);
    }

    // put all files in three' Cache
    for (const fileEntry of allFiles)
    {

        const allowedFileTypes = [
            'png', 'jpg', 'jpeg'
        ];

        const ext = fileEntry.fullPath.split('.').pop();
        if (!allowedFileTypes.includes(ext))
        {
            // console.log("skipping file", fileEntry.fullPath);
            continue;
        }

        const buffer = await getBufferFromFile(fileEntry);
        const img = await imageLoader.loadAsync(URL.createObjectURL(new Blob([buffer])));
        if (debugFileHandling) console.log("caching file", fileEntry.fullPath, img);
        THREE.Cache.add(fileEntry.fullPath, img);
    }

    // TODO we could also allow dropping of multiple MaterialX files (or folders with them inside) 
    // and seed the dropdown from that.
    // At that point, actually reading files and textures into memory should be deferred until they are actually used.
    if (allFiles.length > 0)
    {
        const rootFile = allFiles[0];
        THREE.Cache.add(rootFile.fullPath, await getBufferFromFile(rootFile));

        if (debugFileHandling) console.log("CACHE", THREE.Cache.files);

        loadingCallback(rootFile);
    }
    else
    {
        console.log('No files to add cache.')
    }
}

async function readDirectory(directory)
{
    let entries = [];
    let getEntries = async (directory) =>
    {
        let dirReader = directory.createReader();
        await new Promise((resolve, reject) =>
        {
            dirReader.readEntries(
                async (results) =>
                {
                    if (results.length)
                    {
                        // entries = entries.concat(results);
                        for (let entry of results)
                        {
                            if (entry.isDirectory)
                            {
                                await getEntries(entry);
                            }
                            else
                            {
                                entries.push(entry);
                            }
                        }
                    }
                    resolve();
                },
                (error) =>
                {
                    /* handle error — error is a FileError object */
                },
            )
        }
        )
    };

    await getEntries(directory);
    return entries;
}

async function testAndLoadFile(file)
{
    let ext = file.name.split('.').pop();
    if (debugFileHandling) console.log(file.name + ", " + file.size + ", " + ext);

    const arrayBuffer = await getBufferFromFile(file);
    console.log(arrayBuffer)

    // mock a fileEntry and pass through the same loading logic
    const newFileEntry = {
        fullPath: "/" + file.name,
        name: file.name.split('/').pop(),
        isFile: true,
        file: (callback) =>
        {
            callback(file);
        }
    };

    handleFilesystemEntries([newFileEntry]);
}