const general = require('../general')
const psapi = require('../../psapi')
const html_manip = require('../html_manip')
const layer_util = require('../layer')
const dummy = require('../dummy')
const io = require('../io')
class HordeSettings {
    static {}
    static async saveSettings() {
        try {
            const settings = await getSettings()

            let native_horde_settings = await mapPluginSettingsToHorde(settings)
            const horde_api_key = html_manip.getHordeApiKey()
            native_horde_settings['api_key'] = html_manip.getHordeApiKey()
            await io.IOJson.saveHordeSettingsToFile(native_horde_settings)
        } catch (e) {
            console.warn(e)
        }
    }
    static async loadSettings() {
        try {
            let native_horde_settings =
                await io.IOJson.loadHordeSettingsFromFile()
            html_manip.setHordeApiKey(native_horde_settings['api_key'])
        } catch (e) {
            console.warn(e)
        }
    }
}
class hordeGenerator {
    //horde generation process:
    //*) get the settings
    //*) get send request
    //*) wait for response
    //*) load the image to the canvas
    //*) move and scale image to the selection
    //*) save the image to history/data folder
    //*) load the image data into the plugin / viewer tab
    //*)

    //other options:
    //*)interrupt the generation process
    //*)cancel the generation process on error

    constructor() {
        this.horde_settings
        this.plugin_settings
        this.currentGenerationResult = null
        this.requestStatus = null
        this.isProcessHordeResultCalled = false
        this.maxWaitTime = 0
        this.waiting = 0
        this.isCanceled = false
        this.horde_id = null
        this.last_horde_id = null
    }

    async getSettings() {
        try {
            const workers = await getWorkers()

            const workers_ids = getWorkerID(workers)
            const settings = await getSettings()
            this.plugin_settings = settings
            let payload = await mapPluginSettingsToHorde(settings)
            // payload['workers'] = workers_ids
            payload['workers'] = []

            this.horde_settings = payload
            return this.horde_settings
        } catch (e) {
            console.warn('getSettings: ', e)
        }
    }

    /**
     * @returns {json}{payload, dir_name, images_info, metadata}
     */
    async generateRequest(settings) {
        try {
            this.horde_id = null //reset request_id
            this.requestStatus = await requestHorde(settings)
            if (this.requestStatus?.message) {
                await app.showAlert(this.requestStatus?.message)
            }
            this.horde_id = this.requestStatus.id
            console.log(
                'generateRequest this.requestStatus: ',
                this.requestStatus
            )

            const images_info = await this.startCheckingProgress()
            const result = await this.toGenerationFormat(images_info)
            console.warn('generateRequest() images_info: ', images_info)
            console.warn('generateRequest() result: ', result)

            html_manip.updateProgressBarsHtml(0) // reset progress bar
            return result
        } catch (e) {
            this.horde_id = null
            console.warn(e)
        }
    }
    async generate() {
        //*) get the settings
        this.horde_settings = await this.getSettings()
        //*) send generateRequest() and trigger the progress bar update
        this.isCanceled = false
        const result = await this.generateRequest(this.horde_settings)

        return result
        //*) store the generation result in the currentGenerationResult

        //*) return the generation currentGenerationResult
    }

    isValidGeneration() {
        if (this.currentGenerationResult) {
            return true // if true if valid, false otherwise
        } else {
            return false
        }
    }
    preGenerate() {}
    // async layerToBase64WebpToFile
    //convert layer to .webp file
    //read the .webp file as buffer data base64 .webp
    async layerToBase64Webp(layer, document_name, image_name) {
        const width = html_manip.getWidth()
        const height = html_manip.getHeight()
        const image_buffer = await psapi.newExportPng(
            layer,
            image_name,
            width,
            height
        )

        const base64_image = _arrayBufferToBase64(image_buffer) //convert the buffer to base64
        //send the base64 to the server to save the file in the desired directory
        // await sdapi.requestSavePng(base64_image, image_name)
        await io.saveFileInSubFolder(base64_image, document_name, image_name)
        return base64_image
    }

    async layerToBase64ToFile(layer, document_name, image_name) {
        const width = html_manip.getWidth()
        const height = html_manip.getHeight()
        const image_buffer = await psapi.newExportPng(
            layer,
            image_name,
            width,
            height
        )

        const base64_image = _arrayBufferToBase64(image_buffer) //convert the buffer to base64
        //send the base64 to the server to save the file in the desired directory
        // await sdapi.requestSavePng(base64_image, image_name)
        await io.saveFileInSubFolder(base64_image, document_name, image_name)
        return base64_image
    }

    async toGenerationFormat(images_info) {
        //convert the output of native horde generation to the values that generate() can use
        try {
            //images_info[0] = {path:path,base64:base64png}
            // let last_images_paths = await silentImagesToLayersExe(images_info)
            let last_images_paths = {}
            for (const image_info of images_info) {
                const path = image_info['path']
                // const base64_image = image_info['base64']
                const layer = image_info['layer']
                const [document_name, image_name] = path.split('/')

                // await saveFileInSubFolder(base64_image, document_name, image_name)
                image_info['base64'] = await this.layerToBase64ToFile(
                    layer,
                    document_name,
                    image_name
                )

                // delete the layer made by the webp image.
                await layer_util.deleteLayers([layer])
                // await layer.delete()

                // const json_file_name = `${image_name.split('.')[0]}.json`
                this.plugin_settings['auto_metadata'] =
                    image_info?.auto_metadata

                // g_generation_session.base64OutputImages[path] =
                //     image_info['base64']
                // await saveJsonFileInSubFolder(
                //     this.plugin_settings,
                //     document_name,
                //     json_file_name
                // ) //save the settings
                // last_images_paths[path] = image_info['layer']
                // images_info.push({
                //     base64: i,
                //     path: image_path,
                //     auto_metadata: auto_metadata_json,
                // })
                // // console.log("metadata_json: ", metadata_json)
            }

            // if (g_generation_session.isFirstGeneration) {
            //     //store them in the generation session for viewer manager to use
            //     g_generation_session.image_paths_to_layers = last_images_paths
            // } else {
            //     g_generation_session.image_paths_to_layers = {
            //         ...g_generation_session.image_paths_to_layers,
            //         ...last_images_paths,
            //     }
            //     // g_number_generation_per_session++

            // }
            const dir_name = 'temp_dir_name'
            return {
                // payload: payload,
                dir_name: dir_name,
                images_info: images_info,
                metadata: this.plugin_settings,
            }
        } catch (e) {
            console.warn(e)
        }
    }

    async toSession(images_info) {
        try {
            //images_info[0] = {path:path,base64:base64png}
            // let last_images_paths = await silentImagesToLayersExe(images_info)
            let last_images_paths = {}
            for (const image_info of images_info) {
                const path = image_info['path']
                // const base64_image = image_info['base64']
                const layer = image_info['layer']
                const [document_name, image_name] = path.split('/')

                // await saveFileInSubFolder(base64_image, document_name, image_name)
                image_info['base64'] = await this.layerToBase64ToFile(
                    layer,
                    document_name,
                    image_name
                )
                const json_file_name = `${image_name.split('.')[0]}.json`
                this.plugin_settings['auto_metadata'] =
                    image_info?.auto_metadata

                g_generation_session.base64OutputImages[path] =
                    image_info['base64']
                await io.saveJsonFileInSubFolder(
                    this.plugin_settings,
                    document_name,
                    json_file_name
                ) //save the settings
                last_images_paths[path] = image_info['layer']
            }

            if (g_generation_session.isFirstGeneration) {
                //store them in the generation session for viewer manager to use
                g_generation_session.image_paths_to_layers = last_images_paths
            } else {
                g_generation_session.image_paths_to_layers = {
                    ...g_generation_session.image_paths_to_layers,
                    ...last_images_paths,
                }
                // g_number_generation_per_session++
            }
        } catch (e) {
            console.warn(e)
        }
    }

    async interruptRequest(horde_id) {
        try {
            console.log('interruptRquest():')

            const full_url = `https://stablehorde.net/api/v2/generate/status/${horde_id}`

            console.log(full_url)

            let response = await fetch(full_url, {
                method: 'DELETE',
                headers: {
                    Accept: 'application/json',
                    'Content-Type': 'application/json',
                    // 'Client-Agent': '4c79ab19-8e6c-4054-83b3-773b7ce71ece',
                    'Client-Agent': 'unknown:0:unknown',
                },
            })

            let result = await response.json()
            console.log('interruptReqquest result:', result)

            return result
        } catch (e) {
            console.warn(e)
            return
        }
    }
    async interrupt() {
        try {
            html_manip.updateProgressBarsHtml(0)
            // g_generation_session.request_status = Enum.requestStatus['']
            this.last_horde_id = this.horde_id
            this.horde_id = null //horde_id could be used startCheckingprogress() so we need to nullify it as soon as possible. TODO: refactor this dependency.
            this.isCanceled = true
            // this.interval_id = clearTimeout(this.interval_id)
            await this.interruptRequest(this.last_horde_id)
        } catch (e) {
            console.warn(e)
        }
    }

    async processHordeResult() {
        //*) get the result from the horde server
        //*) save them locally to output directory
        //*) import them into the canvas
        //*) resize and move the layers to fit the selection
        //*) return the results to be stored and processed by the g_generation_session
        try {
            if (this.isProcessHordeResultCalled) {
                return
            }
            this.isProcessHordeResultCalled = true
            console.log('horde request is done')
            // g_b_request_result = true
            const temp_id = this.horde_id //this.horde_id will reset
            // cancelRequestClientSide()
            g_horde_generation_result = await requestHordeStatus(temp_id)

            const generations = g_horde_generation_result.generations
            const writeable_entry = await getCurrentDocFolder()
            const images_info = [] //{path:image_path,base64:}
            for (const image_horde_container of generations) {
                try {
                    const url = image_horde_container.img
                    const image_file_name = general.newOutputImageName('webp')

                    const image_layer = await downloadItExe(
                        url,
                        writeable_entry,
                        image_file_name
                    ) //download the image from url, it works even with .webp format
                    const image_png_file_name =
                        general.convertImageNameToPng(image_file_name)

                    const uuid = await getUniqueDocumentId()
                    const image_path = `${uuid}/${image_png_file_name}` //this is the png path
                    images_info.push({
                        path: image_path,
                        base64: dummy.getDummyBase64(), //TODO:change this to the base64_png
                        layer: image_layer,
                    })
                    await psapi.layerToSelection(
                        g_generation_session.selectionInfo
                    ) //TODO: create a safe layerToSelection function
                } catch (e) {
                    console.warn(e)
                }
            }
            this.isProcessHordeResultCalled = false //reset for next generation
            return images_info
        } catch (e) {
            console.warn(e)
        }
    }
    updateHordeProgressBar(check_horde_status) {
        //update the progress bar proceduer
        console.log('this.maxWaitTime: ', this.maxWaitTime)
        console.log(
            "check_horde_status['wait_time']: ",
            check_horde_status['wait_time']
        )
        console.log(
            "check_horde_status['waiting']: ",
            check_horde_status['waiting']
        )

        this.maxWaitTime = Math.max(
            check_horde_status['wait_time'],
            this.maxWaitTime
        ) // return the max time value, so we could use to calculate the complection percentage
        const delta_time = this.maxWaitTime - check_horde_status['wait_time']

        if (isNaN(this.maxWaitTime) || parseInt(this.maxWaitTime) === 0) {
            this.maxWaitTime = 0 // reset to zero
        } else {
            console.log('delta_time:', delta_time)
            console.log('this.maxWaitTime:', this.maxWaitTime)

            const completion_percentage = (delta_time / this.maxWaitTime) * 100
            console.log('completion_percentage:', completion_percentage)

            html_manip.updateProgressBarsHtml(completion_percentage)
        }
    }
    async startCheckingProgress() {
        console.log('startCheckingProgress is called')
        return await new Promise((resolve, reject) => {
            if (this.horde_id && !this.isCanceled) {
                this.interval_id = setTimeout(async () => {
                    try {
                        console.warn(
                            'startCheckingProgress(): horde_id and isCanceled',
                            this.horde_id,
                            this.isCanceled
                        )
                        //check the request status
                        const check_json = await requestHordeCheck(
                            this.horde_id
                        )

                        this.updateHordeProgressBar(check_json)

                        if (check_json['done']) {
                            // this.interval_id = clearTimeout(this.interval_id)

                            const images_info = await this.processHordeResult()
                            if (this.horde_id) {
                                this.last_horde_id = this.horde_id
                                this.horde_id = null
                            }
                            return resolve(images_info)
                        } else {
                            //the request is not done and the user hasn't canceled it
                            console.warn(
                                'startCheckingProgress(): reqursive startCheckingProgress call',
                                this.horde_id,
                                this.isCanceled
                            )
                            const horde_result =
                                await this.startCheckingProgress() // start another check
                            return resolve(horde_result) // return the result of the new check
                        }
                    } catch (e) {
                        console.warn(e)
                        const result = await this.startCheckingProgress()
                        return resolve(result)
                    }
                }, 3000)
            } else {
                console.warn(
                    'startCheckingProgress: else block',
                    this.horde_id,
                    this.isCanceled
                )
                return resolve()
            }
        })
    }
}
const webui_to_horde_samplers = {
    'Euler a': 'k_euler_a',
    Euler: 'k_euler',
    LMS: 'k_lms',
    Heun: 'k_heun',
    DPM2: 'k_dpm_2',
    'DPM2 a': 'k_dpm_2_a',
    'DPM++ 2S a': 'k_dpmpp_2s_a',
    'DPM++ 2M': 'k_dpmpp_2m',
    'DPM++ SDE': 'k_dpmpp_sde',
    'DPM fast': 'k_dpm_fast',
    'DPM adaptive': 'k_dpm_adaptive',
    'LMS Karras': 'k_lms',
    'DPM2 Karras': 'k_dpm_2',
    'DPM2 a Karras': 'k_dpm_2_a',
    'DPM++ 2S a Karras': 'k_dpmpp_2s_a',
    'DPM++ 2M Karras': 'k_dpmpp_2m',
    'DPM++ SDE Karras': 'k_dpmpp_sde',
    DDIM: 'ddim',
    PLMS: 'plms',
}

//get workers
//select a worker
//send a request => requestHorde(horde_settings)
//check for progress => requestHordeCheck(request_id)
//when progress is full, request the result => requestHordeStatus(request_id)

async function mapPluginSettingsToHorde(plugin_settings) {
    const { getModelHorde } = require('../sd_scripts/horde')
    const ps = plugin_settings // for shortness
    const sampler = webui_to_horde_samplers[ps['sampler_index']]
    const model = getModelHorde()
    let horde_prompt
    if (ps['negative_prompt'].length > 0) {
        horde_prompt = `${ps['prompt']} ### ${ps['negative_prompt']}`
    } else {
        horde_prompt = ps['prompt'] //no negative prompt
    }
    const extra_payload = {}
    if (ps['mode'] === 'img2img') {
        // payload['source_image'] = ps['init_images']
        // let current_doc_entry =await getCurrentDocFolder()
        // let webp_file = await current_doc_entry.getEntry('temp.webp')
        // let base64_webp =    await io.IO.base64WebpFromFile(webp_file)
        // payload['source_image'] = io.IO.base64WebpFromFile()
        // console.log('base64_webp:', base64_webp)

        // const dummy_str = getDummyWebpBase64()
        // if (base64_webp === dummy_str) {
        //     console.warn('the same base64')
        // } else {
        //     console.warn('different base64')
        // }
        // payload['source_image'] = dummy_str

        // payload['source_image'] = base64.b64encode(buffer.getvalue()).decode() //does it need to be webp?

        const init_image_base64_webp = await io.IO.base64PngToBase64Webp(
            ps['init_images'][0]
        )
        extra_payload['source_image'] = init_image_base64_webp
        extra_payload['source_processing'] = 'img2img'
    } else if (ps['mode'] === 'inpaint' || ps['mode'] === 'outpaint') {
        const init_image_base64_webp = await io.IO.base64PngToBase64Webp(
            ps['init_images'][0]
        )
        const mask_base64_webp = await io.IO.base64PngToBase64Webp(ps['mask'])
        extra_payload['source_processing'] = 'inpainting'
        extra_payload['source_image'] = init_image_base64_webp
        extra_payload['source_mask'] = mask_base64_webp
        // payload["source_mask"] = base64.b64encode(buffer.getvalue()).decode()//does it need to be webp?
    }

    let seed = ps['seed']
    if (parseInt(ps['seed']) === -1) {
        const random_seed = Math.floor(Math.random() * 100000000000 + 1) // Date.now() doesn't have enough resolution to avoid duplicate
        seed = random_seed.toString()
    }
    const width = general.nearestMultiple(ps['width'], 64)
    const height = general.nearestMultiple(ps['height'], 64)
    const nsfw = html_manip.getUseNsfw()
    let horde_payload = {
        prompt: horde_prompt,
        params: {
            sampler_name: sampler,
            toggles: [1, 4],
            cfg_scale: ps['cfg_scale'],
            denoising_strength: ps['denoising_strength'],
            seed: seed,
            height: height,
            width: width,
            seed_variation: 1,
            post_processing: ['GFPGAN'],
            karras: false,
            tiling: false,
            steps: parseInt(ps['steps']),
            n: 1,
        },
        nsfw: nsfw,
        trusted_workers: true,
        censor_nsfw: false,
        // workers: ['4c79ab19-8e6c-4054-83b3-773b7ce71ece'],
        // workers: workers_ids,
        // models: ['stable_diffusion'],
        models: [model],
        // source_image: 'string',
        // source_processing: 'img2img',
        // source_mask: 'string',
        ...extra_payload,
        r2: true,
        shared: false,
    }
    return horde_payload
}

function getWorkerID(workers_json) {
    let workers_ids = []
    for (worker of workers_json) {
        workers_ids.push(worker?.id)
    }
    console.log('workers_ids:', workers_ids)

    return workers_ids
}
async function getWorkers() {
    const full_url = 'https://stablehorde.net/api/v2/workers'
    // const full_url = 'https://stablehorde.net/api/v2/generate/sync'
    console.log(full_url)

    let request = await fetch(full_url, {
        method: 'GET',
        headers: {
            Accept: 'application/json',
        },
    })

    let workers = await request.json()
    // const workers_ids = getWorkerID(workers)
    console.log('requestHorde workers:', workers)
    return workers
}
async function requestHorde(payload) {
    // const workers = await getWorkers()

    // const workers_ids = getWorkerID(workers)
    // const settings = await getSettings()
    // payload = mapPluginSettingsToHorde(settings)
    // payload['workers'] = workers_ids
    // payload = {
    //     prompt: 'string',
    //     params: {
    //         sampler_name: 'k_lms',
    //         toggles: [1, 4],
    //         cfg_scale: 5,
    //         denoising_strength: 0.75,
    //         // seed: 'string',
    //         height: 512,
    //         width: 512,
    //         seed_variation: 1,
    //         post_processing: ['GFPGAN'],
    //         karras: false,
    //         tiling: false,
    //         steps: 5,
    //         n: 1,
    //     },
    //     nsfw: false,
    //     trusted_workers: true,
    //     censor_nsfw: false,
    //     // workers: ['4c79ab19-8e6c-4054-83b3-773b7ce71ece'],
    //     workers: workers_ids,
    //     models: ['stable_diffusion'],
    //     // source_image: 'string',
    //     // source_processing: 'img2img',
    //     // source_mask: 'string',
    //     r2: true,
    //     shared: false,
    // }
    try {
        console.log('requestHorde():')

        const full_url = 'https://stablehorde.net/api/v2/generate/async'
        // const full_url = 'https://stablehorde.net/api/v2/generate/sync'
        console.log(full_url)

        const horde_api_key = html_manip.getHordeApiKey()
        let request = await fetch(full_url, {
            method: 'POST',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                apikey: horde_api_key,

                'Client-Agent': 'unknown:0:unknown',
            },
            body: JSON.stringify(payload),
        })

        let json = await request.json()
        console.log('requestHorde json:', json)

        return json
    } catch (e) {
        console.warn(e)
        return {}
    }
}
async function requestHordeCheck(id) {
    try {
        console.log('requestHordeCheck():')
        const base_url = 'https://stablehorde.net/api/v2/generate/check'

        const full_url = `${base_url}/${id}`
        // const full_url = 'https://stablehorde.net/api/v2/generate/sync'
        console.log(full_url)
        const payload = {}
        let request = await fetch(full_url, {
            method: 'GET',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                // 'Client-Agent': '4c79ab19-8e6c-4054-83b3-773b7ce71ece',
                'Client-Agent': 'unknown:0:unknown',
            },
        })

        let json = await request.json()
        console.log('requestHordeCheck json:', json)

        return json
    } catch (e) {
        console.warn(e)
        return {}
    }
}

async function requestHordeStatus(id) {
    try {
        console.log('requestHordeStatus():')
        const base_url = 'https://stablehorde.net/api/v2/generate/status'

        const full_url = `${base_url}/${id}`
        // const full_url = 'https://stablehorde.net/api/v2/generate/sync'
        console.log(full_url)
        const payload = {}
        let request = await fetch(full_url, {
            method: 'GET',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                // 'Client-Agent': '4c79ab19-8e6c-4054-83b3-773b7ce71ece',
                'Client-Agent': 'unknown:0:unknown',
            },
        })

        let json = await request.json()
        console.log('requestHordeStatus json:', json)

        return json
    } catch (e) {
        console.warn(e)
    }
}

let g_horde_generation_result
let g_b_request_result = false
function cancelRequestClientSide() {
    this.interval_id = clearTimeout(this.interval_id)
    // g_id = null
    g_b_request_result = false
}

module.exports = {
    hordeGenerator,
    HordeSettings,
}
