require('linqjs');
const Logger = require('../logs/clsLogger').clsLogger;
const logger = new Logger();
const chalk = require('chalk');
const Player = require('../playerCan/clsPlayer').clsPlayer;

class Downloader {
    constructor({ options = {} }) {
        this._fs = require('fs');
        this._url = require('url');
        this._http = require('http');
        this._https = require('https');
        this._Throttle = require('throttle');
        this._pretty = require('prettysize');
        this._req = this._http;
        this.options = options;
        this.response = null;
        this.tryCount = 0;
        this.player = new Player();

    }

    get(url, isJson, callbackFinished, callbackError) {
        let request = require('request');
        request(url, { json: isJson }, (err, res, body) => {
            if (err) {
                callbackError(err);
            }
            if (res) {
                if (body && res.statusCode == 200) {
                    callbackFinished(body);
                } else {
                    callbackFinished([]);
                }
            }
        });
    }

    prepareheaders(options, headerOnly) {
        const requestOptions = this._url.parse(options.downloadUrl);
        // this part is for proxy users
        let httpoptions = requestOptions;
        httpoptions.headers = {};
        if (options.proxyhost) {
            httpoptions.host = options.proxyhost;
            httpoptions.port = options.proxyport;
            httpoptions.path = options.downloadUrl;
            httpoptions.headers['Connection'] = 'keep-alive';
            httpoptions.headers['Host'] = requestOptions.hostname;
        }
        httpoptions.timeout = options.timeOut;
        if (options.proxyuser) {
            httpoptions.headers['Proxy-Authorization'] = 'Basic ' + new Buffer(options.proxyuser + ':' + options.proxypass).toString('base64');
        }
        if (options.toBytes > 0 && !headerOnly) {
            httpoptions.headers['Range'] = `bytes=${options.fromBytes}-${options.toBytes}`;
        }
        if (headerOnly) {
            httpoptions.method = 'HEAD';
        }
        console.log(JSON.stringify(httpoptions, null, 2))
        return httpoptions;
    }

    async getHeaders(options) {
        let requestOptions = this.prepareheaders(options, true);
        let protocolHandler = (requestOptions.protocol === 'https:' ? this._https : this._http);
        // console.log(requestOptions)
        let result = new Promise((resolve, reject) => {
            let req = protocolHandler.request(requestOptions, (res) => {
                // @ts-ignore
                let f = {
                    fileExists: res.statusCode == 200 ? true : false,
                    // @ts-ignore
                    length: parseInt(res.headers['content-length']),
                    resumeSupport: res.headers['accept-ranges'] == 'bytes' ? true : false,
                    servertype: res.headers['server'],
                    status: res.statusCode,
                    type: res.headers['content-type'],
                    date: res.headers['date'],
                    lastmodified: res.headers['last-modified'],
                    urlExists: true,
                };
                resolve(f);
            });
            req.on('error', (e) => {
                // @ts-ignore
                let f = {
                    fileExists: false,
                    length: 0,
                    resumeSupport: false,
                    servertype: 'unknown',
                    status: 410,
                    type: 'unknown',
                    date: '',
                    lastmodified: '',
                    urlExists: false,
                };
                resolve(f);
                // reject(e)
            });
            req.end();
        });
        return await result;
    }
    downloadFile(file, overwrite, callbackFinished, callbackProgress, callbackError) {
        let options = this.options;
        options.timeout = this.options.downloadTimeout;

        let filename = file.split('/').last();
        options.filename = filename;

        let path = require('path');
        let filepath = path.join(options.directory, options.filename);

        if (this._url.parse(file).protocol === null) {
            file = 'http://' + file;
            this._req = this._http
        } else if (this._url.parse(file).protocol === 'https:') {
            this._req = this._https
        } else {
            this._req = this._http
        }

        //this part is for proxy features
        let httpoptions = {
            host: options.proxyhost,
            port: options.proxyport,
            path: file,
            headers: {
                Connection: 'keep-alive',
                Host: file.split('/')[2]
            }
        };


        if (options.proxyuser != '') {
            httpoptions.headers['Proxy-Authorization'] = 'Basic ' + new Buffer(options.proxyuser + ':' + options.proxypass).toString('base64')
        }
        logger.Downloader('requesting file :' + file);
        let request = this._req.get(options.useproxy == 1 ? httpoptions : file, (response) => {
            this.response = response;
            let len = parseInt(response.headers['content-length'], 10);
            let contentType = response.headers['content-type'];
            let pageStatus = parseInt(response.statusCode);
            let pageMessage = response.statusMessage;
            //logger.Downloader(pageMessage + "-" + pageStatus);
            let cur = 0;
            let total = len / (1024 * 1024); //1048576 - bytes in  1Megabyte
            let speed = 0;
            let lastCur = 0;
            let onemb = 1024 * 1024;
            logger.Downloader('contentType:' + contentType + ',pageStatus:' + pageStatus + ',pageMessage:' + pageMessage + ',length:' + this._pretty(len) + ' (' + len + ' bytes)');
            //if page load is success
            if (response.statusCode === 200) {

                if (this._fs.existsSync(filepath)) {
                    if (overwrite) {
                        this._fs.unlinkSync(filepath);
                    } else {
                        let stats = this._fs.statSync(filepath);
                        let flen = stats["size"];

                        if (len == flen) {
                            logger.Downloader("we already have same file with same size.. skipping");
                            request.abort();
                            if (callbackFinished) {
                                callbackFinished(pageStatus, pageMessage, options.filename, cur, len, filepath);
                            }
                            return;
                        } else {
                            logger.Downloader("file exists but have different size.. deleting..");
                            try {
                                this._fs.unlinkSync(filepath);

                            } catch (e) {
                                logger.Downloader('Error deleting file: ' + e, ' ' + filepath);
                            }
                        }
                    }
                }
                this._intervalid = setInterval(() => {
                    //calculate speed
                    speed = ((cur - lastCur) / 1024).toFixed(0);
                    lastCur = cur;
                }, 1000);
                logger.Downloader('creating file in disk:' + filepath);
                try {
                    var file = this._fs.createWriteStream(filepath);
                    file.on('error', (err) => {
                        logger.Downloader(`Error creating write stream, err: ${err}`);
                    });
                    this.myFile = file;
                } catch (e) {
                    logger.Downloader('Error creating write stream: ' + e);
                }

                if (this.myFile && this.myFile.closed) {
                    this.myFile = null;
                }


                try {
                    if (options.speed > 0) {
                        logger.Downloader(chalk.bold.green(`Setting download speed: ${options.speed} kb/s`));
                        response.pipe(new this._Throttle({ bps: options.speed * 1024, chunkSize: options.speed })).pipe(file)
                    } else {
                        response.pipe(file)
                    }
                } catch (e) {
                    logger.Downloader(`Error throttling download: ${e}`);
                }

            }
            else {

                if (callbackError) {
                    setTimeout(() => {
                        //close stream
                        callbackError(response.statusCode, options.filename)
                    }, 200)
                }
                //if (callbackFinished) callbackFinished(pageStatus,pageMessage,options.filename,master)

            }
            let oldPercentage = -1;
            response.on("data", (chunk) => {
                if (lastCur == 0) {
                    speed = (chunk.length / 1024).toFixed(0)
                }
                cur += chunk.length;
                let percentage = (100.0 * cur / len).toFixed(2);
                let downloaded = (cur / onemb).toFixed(2);
                let totalSize = total.toFixed(2);
                if (pageStatus == 200) {
                    let perc = parseInt(percentage);
                    if ((perc % 25) == 0) {
                        if (oldPercentage != perc) {
                            oldPercentage = perc;
                            logger.Downloader('File name: ' + options.filename + ' percentage: ' + percentage +
                                ' Downloaded: ' + this._pretty(cur) + ' of ' + this._pretty(len) + ' @ ' + this._pretty(speed * 1024) + ' ' + cur + ' ' + len);
                        }
                    }
                    if (callbackProgress) callbackProgress(options.filename, percentage, this._pretty(cur), this._pretty(len), this._pretty(speed * 1024), cur, len);
                }
                //logger.Downloader(  (100.0 * cur / len).toFixed(2) + "% " + (cur / 1048576).toFixed(2) + " mb / " + total.toFixed(2) + " mb");
            });

            response.on("end", () => {
                //let isAudio = false;
                //logger.DownloaderLog(chalk.bgGreen(`${options.filename} is set Downloaded in signage.db`));
                if (this._intervalid != 0) clearInterval(this._intervalid);
                this._intervalid = 0;
                // if(this.options.type == 'DownloadManager') {
                //     let loadFile = this.player.openFile(filepath);
                //     if(loadFile) {
                //         let duration = this.player.getLength(loadFile);
                //         if(duration) {
                //             isAudio = true;
                //         }
                //         duration = null;
                //     } else {
                //         logger.Downloader(`Couldn't load file ${filepath}, is mp3: ${isAudio}`);
                //     }
                //     loadFile = null;
                // }


                //if (pageStatus == 200) callbackProgress(options.filename, 100.0, this._pretty1(cur), this._pretty1(len), this._pretty1(speed * 1024), len, len);
                if (pageStatus == 200) {
                    //this.tryCount = 0;
                    logger.Downloader('Download finished: ' + options.filename + ' ' + this._pretty(cur) + ' of ' + this._pretty(len) + ' @' + this._pretty(speed * 1024));
                    if (callbackFinished) {

                        setTimeout(() => {

                            callbackFinished(pageStatus, pageMessage, options.filename, cur, len, filepath);

                        }, 200)
                    }
                }
            });

            request.setTimeout(options.timeout, () => {
                request.abort();
                if (this._intervalid != 0) clearInterval(this._intervalid);
                this._intervalid = 0;
                pageMessage = 'Timeout';
                pageStatus = 512;
                logger.Downloader(chalk.red('download timeout for ' + options.filename));
                if (callbackError) callbackError("Timeout", options.filename)
                //if (callbackFinished) callbackFinished(pageStatus,pageMessage,options.filename)
            })
        }).on("error", (e) => {
            logger.Downloader(chalk.bold.red(`error downloading ${options.filename}, msg: ${e.message}`));
            if (e.code == 'ENOTFOUND') {
                logger.Downloader(`Error: err: ${e} - ${file} not found on host`)
            }
            if (this._intervalid != 0) clearInterval(this._intervalid);
            this._intervalid = 0;
            if (callbackError) callbackError(e.message, options.filename);
            if (callbackFinished) callbackFinished(513, e.message, options.filename)
        });
    }
}

module.exports = {
    Downloader: Downloader
};
