require('linqjs');
const fs = require('fs');
const Downloader = require('../downloader/Downloader').Downloader;
const _ = require('lodash');
const schedule = require('node-schedule');
const Diyanet = class Diyanet {
    constructor({ logger, config, moment } = {}) {
        this.regex = /<td>([0-9]{2})\s(.+)\s([0-9]{4})\s(.+)<\/td>\r?\n.+<td>([0-9:]{5}).+\r?\n.+<td>([0-9:]{5}).+\r?\n.+<td>([0-9:]{5}).+\r?\n.+<td>([0-9:]{5}).+\r?\n.+<td>([0-9:]{5}).+\r?\n.+<td>([0-9:]{5}).+\r?\n/g;
        this.logger = logger;
        this.config = config;
        this.moment = moment;
        this.volumeDownJobName = 'prayerDown';
        this.volumeUpJobName = 'prayerUp';
        this.dailyCheckJobName = 'dailyCheckJob';
        this.prayerJobName = 'prayer';
        this.prayerJson = 'prayerTimes.json';
        this.townsJson = 'towns.json';
        this.prayerPerDay = 6;
        this.itsPrayerTime = false;
        this.minimumPrayerTimes = this.prayerPerDay * 5; // minumum 30 prayer time (5 days of data);
        this.options = {
            directory: process.cwd(),
            filename: '',
            speed: 0,
            useTiming: true,
            startTime: '00:00:01',
            endTime: '23:59:59',
            downloadTimeout: 60000,
            type: 'Diyanet',
            useproxy: parseInt(config.proxy.proxyEnabled),
            proxyuser: config.proxy.proxyUser,
            proxypass: config.proxy.proxyPass,
            proxyport: config.proxy.proxyPort,
            proxyhost: config.proxy.proxyHost
        };
        this.months = {
            Ocak:'01',
            Subat:'02',
            Mart:'03',
            Nisan:'04',
            Mayis:'05',
            Haziran:'06',
            Temmuz:'07',
            Agustos:'08',
            Eylul:'09',
            Ekim:'10',
            Kasim:'11',
            Aralik:'12'
        };
        this.eventTypes = {
            prayerBefore: 'prayerBefore',
            prayerAfter: 'prayerAfter'
        };
        this.downloader = new Downloader({options: this.options});
        // check daily jobs and set new jobs if prayer time is enabled.
    }

    writeFile(str,filename,callback){
        fs.writeFile(filename, str, 'utf8', callback);
    }

    getSehirler(countryCode, callback, callbackErr) {
        let filename = 'sehirler.json';
        let str = 'https://namazvakitleri.diyanet.gov.tr/tr-TR/home/GetRegList?ChangeType=country&CountryId=' + countryCode + '&Culture=tr-TR';
        this.downloader.get(str, true, (json) => {
            this.writeFile(JSON.stringify(json.StateList, null, 2), filename, () => {
                this.logger.PrayerTime(`Citys written in ${filename}`)
            });
            callback(json.StateList);
        }, (err) => {
            this.logger.PrayerTime(`Error getting citys: ${err}`);
            callbackErr(err);
        });
    }

    getIlceler(countryCode, cityCode, callback, callbackErr) {
        let filename = this.townsJson;
        let str = 'https://namazvakitleri.diyanet.gov.tr/tr-TR/home/GetRegList?ChangeType=state&CountryId=' + countryCode + '&Culture=tr-TR&StateId=' + cityCode;
        this.downloader.get(str, true, (json) => {
            this.writeFile(JSON.stringify(json.StateRegionList, null, 2), this.townsJson, () => {
                this.logger.PrayerTime(`Towns written in ${this.townsJson}`);
                callback(json.StateRegionList);
            })
        }, (err) => {
            this.logger.PrayerTime(`Error getting towns: ${err}`);
            callbackErr(err);
        });
    }

    getPrayerTimes(townCode, callback, callbackErr) {
        let str = 'https://namazvakitleri.diyanet.gov.tr' + townCode;
        let decoded;
        this.downloader.get(str, false, (json) => {
            let m, t1 = [];
            if(json) {
                decoded = this.decodeUnicode(json)
            } else {
                this.logger.PrayerTime(`Error retrieving data from ${str}, data we have: ${json}. skipping.`);
                return;
            }
            while ((m = this.regex.exec(decoded)) !== null) {
                // This is necessary to avoid infinite loops with zero-width matches
                if (m.index === this.regex.lastIndex) {
                    this.regex.lastIndex++;
                }
                let date = m[3] + '-' + this.months[ this.replaceTurkishChars(m[2])] + '-' + m[1];
                let obj = [date,m[5],m[6],m[7],m[8],m[9],m[10]];
                for(var i = 1; i < obj.length; i++){
                    t1.push(date + 'T' + obj[i] + ':00')
                }
                obj = null;
                date = null;
            }
            t1 = _.toArray(t1.distinct());
            this.writeFile(JSON.stringify(t1, null, 2), this.prayerJson, () => {
                this.logger.PrayerTime(`Prayer times written in: ${this.prayerJson}`);
            });
            callback(t1);
        }, (err) => {
            this.logger.PrayerTime(`Error getting all prayer times: ${err}`);
            callbackErr(err);
        })
    }

    getLocalPrayerTimes(callback, callbackErr) {
        let now = this.moment().utc(true);
        // if there's no local data, get data.
        if(!fs.existsSync(this.prayerJson)) {
            this.getPrayerTimes(this.config.PrayerTime.townUrl, async (data) => {
                this.logger.PrayerTime(`New prayer times: ${JSON.stringify(data)}`);
                let prayersAfterNow =  _.toArray(data.where(c => this.moment(c).utc(true).diff(now, 'seconds') >= 0).select(d => d));
                callback(prayersAfterNow);
            }, (err) => {
                this.logger.PrayerTime(`Error getting local prayer times: ${err}, ${this.prayerJson} doesnt exist on drive`);
                callbackErr(err);
            })
        } else {
            try {
                //if we have data, check data if there're more then 30 after now.
                // if namazjson doesnt have array, it leads to json parse error, and we cant get new prayer times.
                let namazJson = fs.readFileSync(this.prayerJson, 'utf8');
                // so if its empty, add '[]' in memory to avoid this problem.
                if(namazJson == '') namazJson = '[]';
                let readPrayerTimes = JSON.parse(namazJson);
                let prayersAfterNow = _.toArray(readPrayerTimes.where(c => this.moment(c).utc(true).diff(now, 'seconds') >= 0).select(d => d));
                if(prayersAfterNow.length < this.minimumPrayerTimes) {
                    this.getPrayerTimes(this.config.PrayerTime.townUrl, async (data) => {
                        this.logger.PrayerTime(`We had ${prayersAfterNow.length} data, pulled new data: ${JSON.stringify(data)}`);
                        callback(data);
                    }, (err) => {
                        this.logger.PrayerTime(`Error getting local prayer times: ${err}, ${this.prayerJson} exist in drive`);
                        callbackErr(err);
                    })
                    // if we have enough, return what we have.
                } else {
                    callback(prayersAfterNow);
                }
            } catch (e) {
                // if namazsaatleri json doesnt have chars [], it may lead to syntax err.
                this.logger.PrayerTime(`We have ${this.prayerJson} but its probably empty, Err: ${e}`);
                callback([]);
            }
        }
    }

    async getPrayerFromServer(callback, callbackErr) {
        let url = `${this.config.PrayerTime.prayerUrl}${this.config.PrayerTime.countryID}-${this.config.PrayerTime.cityID}-${this.config.PrayerTime.townID}.json`;
        this.downloader.get(url, true, (data) => {
            this.writeFile(JSON.stringify(data, null, 2), this.prayerJson, () => {
                this.logger.PrayerTime(`Prayer times written in: ${this.prayerJson}`);
                callback(data);
            });
        }, (err) => {
            this.logger.PrayerTime(`Error getting prayer times from ${url}`);
            callbackErr(err);
        });
    }

    decodeUnicode(str) {
        return str.replace(/&#(\d+);/g, function (match, dec) {
            return String.fromCharCode(dec);
        });
    }

    replaceAll(find, replace, str) {
        return str.replace(new RegExp(find, 'g'), replace);
    }

    replaceTurkishChars(str){
        let tmp = this.replaceAll('Ü', 'U', str);
        tmp = this.replaceAll('Ğ', 'G', tmp);
        tmp = this.replaceAll('Ş', 'S', tmp);
        tmp = this.replaceAll('İ', 'I', tmp);
        tmp = this.replaceAll('Ö', 'O', tmp);
        tmp = this.replaceAll('Ç', 'C', tmp);
        tmp = this.replaceAll('ü', 'u', tmp);
        tmp = this.replaceAll('ğ', 'g', tmp);
        tmp = this.replaceAll('ş', 's', tmp);
        tmp = this.replaceAll('ı', 'i', tmp);
        tmp = this.replaceAll('ö', 'o', tmp);
        tmp = this.replaceAll('ç', 'c', tmp);
        tmp = this.replaceAll('&#199;', 'C', tmp);
        return tmp;
    }

    dailyCheckJob() {
        let rule = new schedule.RecurrenceRule();
        rule.hour = this.config.PrayerTime.checkHour;
        rule.minute = this.config.PrayerTime.checkMinute;
        let job = schedule.scheduleJob(this.dailyCheckJobName, rule, () => {
            this.logger.PrayerTime('Daily check for prayer times executed');
            this.start();
        });
    }

    start() {
        this.logger.PrayerTime('Prayer times are being read');
        let now = this.moment().utc(true);
        let jobCount = 0;
        for(let key in schedule.scheduledJobs) {
            if(key.startsWith(this.prayerJobName)) {
                // increment job count to see how many prayer time jobs we have.
                jobCount++;
            }
        }
        let localJson= [];
        let afterNow = [];
        // if prayerjson exists, it's a dailycheck or a restart, see if we have enough jobs then see if we have enough jobs in prayerjson,
        // if not pull new data from our server.
        // else its a cold start, pull prayer times data from server and set schedules for them.
        if(fs.existsSync(this.prayerJson)) {
            let json = fs.readFileSync(this.prayerJson, 'utf8');
            try {
                localJson = JSON.parse(json);
            } catch (e) {
                this.logger.PrayerTime(`Error parsing json: ${e}, json: ${json}`);
            }

            // added isArray control for localJson incase prayerTimes.json is html body from a prior request
            afterNow = _.isArray(localJson) ? localJson.where(c => this.moment(c).utc(true).diff(now, 'seconds') >= 0).select(d => d) : [];
            if(jobCount < this.minimumPrayerTimes) {
                if(afterNow.length > this.minimumPrayerTimes) {
                    this.logger.PrayerTime(`Creating jobs for ${afterNow.length} prayer times`);
                    this.setCronJobs(afterNow);
                    return;
                }
                this.getPrayerFromServer((data) => {
                    let prayersAfterNow = _.toArray(data.where(c => this.moment(c).utc(true).diff(now, 'seconds') >= 0).select(d => d));
                    if(!prayersAfterNow) {
                        this.logger.PrayerTime(`Couldn't retrieve data from server`);
                    }
                    this.logger.PrayerTime(`Creating jobs for ${prayersAfterNow.length} prayer times`);
                    this.setCronJobs(prayersAfterNow);
                }, (err) => {

                });
            } else {
                this.logger.PrayerTime(`Have enough data, no need to set jobs.`);
            }
        } else {
            this.getPrayerFromServer((data) => {
                let prayersAfterNow = _.toArray(data.where(c => this.moment(c).utc(true).diff(now, 'seconds') >= 0).select(d => d));
                this.logger.PrayerTime(`Creating jobs for ${prayersAfterNow.length} prayer times`);
                this.setCronJobs(prayersAfterNow);
            }, (err) => {

            });
        }
        // this.getLocalPrayerTimes((done) => {
        //     // let prayersAfterNow = _.toArray(done.where(c => this.moment(c).utc(true).diff(now, 'seconds') >= 0).select(d => d));
        //     let jobCount = 0;
        //     for(let key in schedule.scheduledJobs) {
        //         if(key.startsWith(this.prayerJobName)) {
        //             // increment job count to see how many prayer time jobs we have.
        //             jobCount++;
        //         }
        //     }
        //     // all data is after now, no need to worry about past prayer times.
        //     //if we have less then 30 days of data, create jobs for new ones.
        //     if(done.length < this.minimumPrayerTimes || jobCount < this.minimumPrayerTimes) {
        //         if(done.length > 0) {
        //             this.setCronJobs(done);
        //         }
        //     } else {
        //         this.logger.PrayerTime(`We have ${jobCount} jobs for prayer times, no need to create cron jobs`);
        //     }
        // }, (err) => {
        //     setTimeout(() => {
        //         this.logger.PrayerTime(`Error getting local prayer times in start method: ${err}`);
        //         this.start();
        //     }, 5000);
        //
        // })
    }

    clearEx(exception) {
        this.logger.PlayerEventsAnnounces(`Clearing old ${exception} jobs`);
        let cnt = 0;
        for (let key in schedule.scheduledJobs) {
            if (key.startsWith(exception)) {
                schedule.scheduledJobs[key].cancel();
                cnt++;
            }
        }
        this.logger.PlayerEventsAnnounces(`Cleared ${cnt} old ${exception} jobs`);
    }

    setCronJobs(prayerTimes) {
        let jobCnt = 0;
        let data = prayerTimes.orderBy(c => c).distinct();
        // clear all volume up and down jobs for prayers.
        this.clearEx(this.prayerJobName);
        data.forEach((c) => {
            // set volume up and down times according to config volume up and down
            let before = this.moment(c).utc(true).add(parseInt(this.config.PrayerTime.volumeDown), 'minutes').toJSON().substr(0,19);
            let after = this.moment(c).utc(true).add(parseInt(this.config.PrayerTime.volumeUp), 'minutes').toJSON().substr(0,19);
            this.logger.PrayerTime(`Ezan saati: ${c} - down @ ${before} - up @ ${after}`);
            this.logger.PrayerTime('---------------------------------------------------------------------------------------');
            let downJob = schedule.scheduleJob(this.volumeDownJobName + jobCnt, this.moment(before).toDate(), (x) => {
                this.logger.PrayerTime(`Prayer volume down event fired for: ${x}`);
                this.emit(this.eventTypes.prayerBefore);
            });
            let upJob = schedule.scheduleJob(this.volumeUpJobName + jobCnt, this.moment(after).toDate(), (x) => {
                this.logger.PrayerTime(`Prayer volume up event fired for: ${x}`);
                this.emit(this.eventTypes.prayerAfter);
            });
            jobCnt++;
        });
        if(prayerTimes.length > 0) {
            this.logger.PrayerTime(`Created ${jobCnt} jobs for both volume up and volume down events`);
            let first = prayerTimes.first();
            let before = this.moment(first).add(this.config.PrayerTime.volumeDown, 'minutes').utc(true).toJSON().substr(0, 19);
            let after = this.moment(first).add(this.config.PrayerTime.volumeUp, 'minutes').utc(true).toJSON().substr(0, 19);
            this.logger.PrayerTime(`Closest prayer time is @ ${first} - down @ ${before} - up @ ${after}`)
        }
    }

    async deleteJson(path) {
        if(fs.existsSync(path)) {
            try {
                await fs.unlinkSync(path);
            } catch (e) {
                this.logger.PrayerTime(`Error deleting ${path}: ${e}`);
            }
        }
    }
};

let util = require("util");
let EventEmitter = require("events").EventEmitter;
util.inherits(Diyanet, EventEmitter);
module.exports = Diyanet;
