const EventEmitter = require("events").EventEmitter;
const util = require("util");
const chalk = require("chalk");
const to = require("await-to-js").to;
require("linqjs");

const schedulesDB = class schedulesDB {
    constructor({ db, moment, config, logger } = {}) {
        this.db = db;
        this.moment = moment;
        this.config = config;
        this.Schedules_Details = this.db.Schedules_Details;
        this.Schedules = this.db.Schedules;
        this._sequelize = this.db._sequelize;
        this.LastPlayedSongs = this.db.LastPlayedSongs;
        this.logger = logger;
        //this.sutils = new smgUtils();
        this.eventTypes = {
            ScheduleInserted: 'ScheduleInserted'
        }
    }

    async updateScheduleFileDownloadedStatus(status, filename) {
        let err, res;
        [err, res] = await to(
            this.Schedules_Details.update(
                { isDownloaded: status },
                { where: { FileName: filename } }
            )
        );
    }

    async getAllSchedulesWithDetails() {
        return await this.Schedules.findAll({ include: ["Schedules_Details"] });
    }

    async getSchedulesInfo() {
        let now = this.moment().utc(true);
        let nowj = now.toJSON().substr(0, 11);
        let schedules = await await this.Schedules.findAll({
            where: { deviceID: this.config.Settings.deviceID }
        });
        let q = schedules.select(c => {
            return {
                scheduleID: c.scheduleID,
                scheduleName: c.scheduleName,
                deviceID: c.deviceID,
                startDate: this.moment(c.startDate + "T" + c.startTime).utc(true),
                endDate: this.moment(c.endDate + "T" + c.endTime).utc(true),
                startTime: this.moment(nowj + c.startTime).utc(true),
                endTime: this.moment(nowj + c.endTime).utc(true),
                daysEffected: c.daysEffected,
                isActive: c.isActive,
                hasPriority: c.hasPriority,
                Volume: c.Volume,
                isShuffleEnabled: c.isShuffleEnabled
            };
        });

        return q;
    }

    async getSchedulesOnly() {
        let now = this.moment().utc(true);
        let nowj = now.toJSON().substr(0, 11);
        try {
            let schedules = await (await this.Schedules.findAll({
                where: { deviceID: this.config.Settings.deviceID }
            })).select(c => {
                return {
                    scheduleID: c.scheduleID,
                    scheduleName: c.scheduleName,
                    deviceID: c.deviceID,
                    startDate: this.moment(c.startDate + "T" + c.startTime).utc(true),
                    endDate: this.moment(c.endDate + "T" + c.endTime).utc(true),
                    startTime: this.moment(nowj + c.startTime).utc(true),
                    endTime: this.moment(nowj + c.endTime).utc(true),
                    daysEffected: c.daysEffected,
                    isActive: c.isActive,
                    hasPriority: c.hasPriority,
                    Volume: c.Volume,
                    isShuffleEnabled: c.isShuffleEnabled,
                    lastUpdateDate: c.lastUpdateDate
                };
            });
            if (schedules.length > 0) {
                return schedules;
            }
        } catch (e) {
            this.logger.System("Error fetching schedules from db: " + e.message);
            return [];
        }
    }

    async getSchedules() {
        let now = this.moment().utc(true);
        let nowj = now.toJSON().substr(0, 11);
        try {
            let err, data;
            [err, data] = await to(
                this.Schedules.findAll({
                    include: ["Schedules_Details"],
                    where: { deviceID: this.config.Settings.deviceID }
                })
            );
            //let schedules = await (await this.Schedules.findAll({include: ['Schedules_Details'], where: {deviceID: this.config.Settings.deviceID}}))
            if (err) {
                this.logger.System("Error fetching schedules from db: " + err);
                return [];
            }
            let schedules = data.select(c => {
                return {
                    scheduleID: c.scheduleID,
                    scheduleName: c.scheduleName,
                    deviceID: c.deviceID,
                    startDate: this.moment(c.startDate + "T" + c.startTime).utc(true),
                    endDate: this.moment(c.endDate + "T" + c.endTime).utc(true),
                    startTime: this.moment(nowj + c.startTime).utc(true),
                    endTime: this.moment(nowj + c.endTime).utc(true),
                    daysEffected: c.daysEffected,
                    isActive: c.isActive,
                    hasPriority: c.hasPriority,
                    Volume: c.Volume,
                    isShuffleEnabled: c.isShuffleEnabled,
                    lastUpdateDate: c.lastUpdateDate,
                    Schedules_Details: c.Schedules_Details.select(
                        d => d.dataValues
                    ).orderBy(e => e.orderID)
                };
            });
            if (schedules.length > 0) {
                return schedules;
            }
        } catch (e) {
            this.logger.System("Error fetching schedules from db: " + e.message);
            return [];
        }
    }

    async getSchedulesNowWithFiles() {
        let q = (await this.db.schedulesNowWithFiles.findAll()).select(
            c => c.dataValues
        );
        let obj = {
            startTime: this.moment(q.min(c => c.startTime)).utc(true),
            endTime: this.moment(q.max(c => c.endTime)).utc(true),
            Volume: q.max(c => c.Volume),
            isShuffleEnabled: q.first().isShuffleEnabled,
            Schedules_Details: q
        };
        return obj;
    }
    async getSchedulesToPlay() {
        let now = this.moment().utc(true);
        //weekdays are 0-sunday, 1-monday, 2-tuesday ...
        //we store them as 1,2,4,8,16,32,64 which is 2^weekday
        //so alldays represented as 127 if we sum them.
        //if we want to know if playlist can play on wednesday (weekday 3)  which is 2^3=8
        //so we just ask to the database (daysEffected & today)==today  => (127 & 8)==8.
        // if it gives result 8, this means that we can play on wednesday
        //_____________________________
        //| s | m | t | w | t | f | s |
        //|---|---|---|---|---|---|---|
        //| 0 | 1 | 2 | 3 | 4 | 5 | 6 | weekday
        //|---|---|---|---|---|---|---|
        //|2^0|2^1|2^2|2^3|2^4|2^5|2^6|pow of 2 operation
        //|---|---|---|---|---|---|---|
        //| 1 | 2 | 4 | 8 |16 |32 |64 |bit values, total 127, 'daysEffected' column is stored in schedules
        //|___|___|___|___|___|___|___|with one column, we can store whole week values

        let today = Math.pow(2, now.weekday());
        let data = await this.getSchedules();
        if (!data) return null;
        let q = data.where(
            c =>
                now.isBetween(c.startDate, c.endDate, null, "[]") &&
                now.isBetween(c.startTime, c.endTime, null, "[]") &&
                (c.daysEffected & today) == today &&
                c.isActive
        );

        let q1 = q.where(c => c.hasPriority);

        if (q1.length > 0) {
            q = q1;
        }
        if (q.length == 0) {
            return null;
        }

        let obj = {
            startTime: q.min(c => c.startTime),
            endTime: q.max(c => c.endTime),
            Volume: q.max(c => c.Volume),
            isShuffleEnabled: q.first().isShuffleEnabled,
            lastUpdateDate: q.max(c => c.lastUpdateDate),
            hasPriority: q.first().hasPriority,
            Schedules_Details: this.getSchedulesFilesToPlay(q)
        };

        return obj;
    }

    getSchedulesFilesToPlay(schedules) {
        if (schedules.length == 0 && schedules == null) return [];

        let arr = [];

        schedules.forEach(c =>
            c.Schedules_Details.forEach(d => {
                d.isPlayed = false;
                d.isDownloaded = d.isDownloaded == 0 ? false : true;

                d.isSchedules = true;
                arr.push(d);
            })
        );

        arr = arr.orderBy(c => c.orderID);
        return arr;
    }

    clearSchedules() {
        //region clear schedules and details
        this.Schedules.destroy({
            where: { deviceID: { gt: 0 } }
        }).then(
            this.Schedules_Details.destroy({
                where: { scheduleID: { gt: 0 } }
            })
        );
        //endregion
    }

    async getSchedulesLastUpdateDate() {
        let q = await await this.Schedules.findAll({
            attributes: [
                [
                    this._sequelize.fn("MAX", this._sequelize.col("lastUpdateDate")),
                    "lastUpdateDate"
                ]
            ]
        });

        return q[0].lastUpdateDate;
    }

    async InsertSchedules(data) {
        this.logger.DownloadSchedules(
            chalk.blue("we have " + data.length + " new schedules to add")
        );
        let schedules = data.select(c => {
            return {
                scheduleID: c.sc.scheduleID,
                scheduleName: c.sc.scheduleName,
                deviceID: c.sc.deviceID,
                startDate: c.sc.startDate.substring(0, 10),
                endDate: c.sc.endDate.substring(0, 10),
                startTime: c.sc.startTime.substring(11, 19),
                endTime: c.sc.endTime.substring(11, 19),
                isLooping: c.sc.isLooping,
                delayTime: c.sc.delayTime,
                daysEffected: c.sc.daysEffected,
                createdDate: c.sc.createdDate.substring(0, 19),
                isActive: c.sc.isActive,
                hasPriority: c.sc.hasPriority,
                orderID: c.sc.orderID,
                lastUpdateDate: c.sc.lastUpdateDate.substring(0, 19),
                Volume: c.sc.Volume,
                isShuffleEnabled: c.sc.isShuffleEnabled
            };
        });
        //each schedule have one or more details. so we have to extract them from data.
        let dat = [];
        let cnt = 1;
        data.forEach(c =>
            c.scd.forEach(d => {
                d.ID = cnt++;
                dat.push(d);
            })
        );

        let details = dat.select(c => {
            return {
                scheduleID: c.scheduleID,
                FileID: c.FileID,
                ID: c.ID,
                FileName: c.file,
                OriginalName: c.ofile,
                orderID: c.order,
                FileSize: c.size,
                isDownloaded: this.db._downloadStatusenum.NotDownloaded
            };
        });

        data = null;
        dat = null;
        this.logger.DownloadSchedules("deleting old schedules and details");
        await this.Schedules.destroy({ where: { deviceID: { gt: 0 } } });
        await this.Schedules_Details.destroy({ where: { scheduleID: { gt: 0 } } });

        this.logger.DownloadSchedules("inserting new schedules and details");
        if (schedules.length > 0) await this.Schedules.bulkCreate(schedules);
        if (details.length > 0) await this.Schedules_Details.bulkCreate(details);
        if(schedules.length > 0 || details.length > 0) {
            this.emit(this.eventTypes.ScheduleInserted);
        }
        this.logger.DownloadAnnounces(
            "inserting new Schedules and details completed"
        );
    }

    async setLastPlayedSong(scheduleID, FileName) {
        let q1 = await this.LastPlayedSongs.findOne({
            where: { playlistID: scheduleID }
        });
        if (!q1) {
            //insert
            let q = await this.LastPlayedSongs.create({
                playlistID: scheduleID,
                FileName: FileName
            });
            //console.log(q)
        } else {
            //update
            let res, err;
            [err, res] = await to(
                this.LastPlayedSongs.update(
                    { playlistID: scheduleID, FileName: FileName },
                    { where: { playlistID: scheduleID } }
                )
            );
            //console.log(res)
        }
    }

    async getLastPlayedSong(scheduleID) {
        let q = await this.LastPlayedSongs.findOne({
            where: { playlistID: scheduleID }
        });
        if (q) {
            return q.dataValues;
        } else {
            return null;
        }
    }

    async todaysTimeExceptions() {
        let q = (await this.db.todaysTimeExceptions.findAll()).select(
            c => c.dataValues
        );
        if (q.length == 0) {
            return null;
        }
        return q
            .select(c => {
                return {
                    startTime: this.moment(c.startTime).utc(true),
                    endTime: this.moment(c.endTime).utc(true)
                };
            })
            .first();
    }
};

util.inherits(schedulesDB, EventEmitter);
module.exports = schedulesDB;
