import readline from 'readline';
import puppeteer from 'puppeteer';

import { config } from './config';

declare global {
    interface String {
        capitalize(): string;
    }
}

String.prototype.capitalize = function (this: string) {
    return this.toLowerCase().replace(/(?:^|\s|["'([{])+\S/g, match => match.toUpperCase());
};

export const week = new Array<day>('monday', 'tuesday', 'wednesday', 'thursday', 'friday');

export function log(param: string, pad = config.CONSOLESIZE): void {
    param = ` ${param} `;
    const start = (pad - param.length) / 2 + param.length;
    console.log(param.padStart(Math.round(start), '-').padEnd(pad, '-'));
}

export function confirm(message: string) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    return new Promise<boolean>(response => {
        rl.question(`\x1b[34m?\x1b[0m ${message} (Y/N)? `, (answer) => {
            process.stdout.moveCursor(0, -1);
            process.stdout.clearScreenDown();
            response(new Array('y', 'Y', 's', 'S', '').includes(answer.trim()));
            rl.close();
        });
    });
}

export function askUser() {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    return new Promise<string>(resolve => {
        rl.question('User: ', user => {
            resolve(user);
            rl.close();
        })
    });
}

export function password() {
    const rl: any = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });
    const stdoutMuted = true;
    const query = "Password : ";

    rl._writeToOutput = function _writeToOutput(stringToWrite: string) {
        const animation = (rl.line.length % 2 == 1) ? "=-" : "-=";
        if (stdoutMuted)
            process.stdout.write(`\x1B[2K\x1B[200D${query}[${animation}]`);
        else
            process.stdout.write(stringToWrite);
    };

    return new Promise<string>(resolve => {
        rl.question(query, function (password: string) {
            process.stdout.write('\n');
            resolve(password);
            rl.close();
        });
    });
}

export function progressBar(length = 0, count = 0): void {
    const percent = count / length;
    const percent_handle = isNaN(percent) ? 0 : percent;
    const percent_fixed = Math.ceil(percent_handle * 100);
    const size = config.CONSOLESIZE - 4 - percent_fixed.toString().length;
    const bar = '.'.repeat(size * percent_handle).padEnd(size, " ");

    if (percent_handle === 1) {
        process.stdout.write(`\r[${bar}] ${percent_fixed}%`);
        process.stdout.write(`\r`);
    } else {
        process.stdout.write(`\r[${bar}] ${percent_fixed}%`);
    }
}

export function selectUEAS(ueas_options: uea[]) {
    const ueas = ueas_options.map(option => {
        return { ...option, choosed: false } as uea & { choosed?: boolean };
    });

    let index = 0;
    let toogle = false;

    const menu = () => {
        if (!!toogle) {
            process.stdout.moveCursor(0, -(ueas.length - 1));
            process.stdout.cursorTo(0);
            process.stdout.clearScreenDown();
        } else {
            toogle = true;
        }

        ueas.forEach(({ name, choosed }, i) => {
            let option = (i === index)
                ? `\x1b[35m> ${name}`
                : name;

            if (!!choosed) {
                option = `\x1b[4m${option}`;
            }
            process.stdout.write(`${option}\x1b[0m${i !== ueas.length - 1 ? '\n' : ''}`);
        });
    }

    return new Promise<uea[]>((resolve, rejected) => {
        console.log('\x1b[34m?\x1b[0m %s', 'Choose what subjects are taken');
        console.log('\x1b[31m!\x1b[0m %s', 'Press [Esc] to resume process');
        readline.emitKeypressEvents(process.stdin);
        if (process.stdin.isTTY) {
            process.stdin.setRawMode(true);
            process.stdin.resume();
            process.stdin.on('keypress', (_, { name, ctrl }) => {
                if (name === 'down' && index < ueas.length - 1) {
                    ++index;
                } else if (name === 'up' && index > 0) {
                    --index;
                }

                if (name === 'return') {
                    ueas[index].choosed = !ueas[index].choosed;
                }

                if (name === 'escape' || (name === 'c' && ctrl)) {
                    process.stdin.setRawMode(false);
                    process.stdin.pause();

                    if (name === 'c' && ctrl)
                        process.exit(0);

                    process.stdout.moveCursor(0, -(ueas.length + 1));
                    process.stdout.cursorTo(0);
                    process.stdout.clearScreenDown();
                    resolve(ueas.filter(option => {
                        const choosed = option.choosed;
                        delete option.choosed;
                        return choosed;
                    }));
                    process.stdin.removeAllListeners('keypress');
                } else {
                    menu();
                }
            });

            menu();
        } else {
            rejected('Idk man, you r ugly');
        }
    });
}

export async function tryDOM<T>(callback: () => Promise<T extends void ? never : T | null | undefined>, DOM: puppeteer.Page) {
    let response: T | null | undefined;
    let response_tries = 0;

    do {
        response = await callback();

        if (response_tries === config.TRIES) {
            throw new Error("Element from DOM couldn´t load property");
        } else if (!response) {
            await DOM.waitForTimeout(config.TIMEOUT);
            ++response_tries;
        }

        if (response instanceof Array && response.length < 1) {
            response = undefined;
        }
    } while (!response);

    return response;
}

export function timeParse(time: string): time | null {
    const [starts, ends] = time.slice(0, 13).trim().split(' - ');

    return time.trim().length > 1
        ? { starts, ends }
        : null;
}

export function timeStringify(time: number): string {
    const timeDate = new Date(time);
    const hours = timeDate.getHours().toString();
    const minutes = timeDate.getMinutes().toString();
    return (hours.length < 2 ? 0 + hours : hours) + ':' + (minutes.length < 2 ? 0 + minutes : minutes);
}

export function selectTeachers(teachers_options: Set<string>) {
    let teachers = Array.from(teachers_options).map(name => {
        return { name, choosed: false } as { name: string; choosed?: boolean; };
    }).sort(({ name: a }, { name: b }) => (a < b) ? -1 : ((a > b) ? 1 : 0));

    let index = 0;
    let toogle = false;

    const menu = () => {
        if (!!toogle) {
            process.stdout.moveCursor(0, -(teachers.length - 1));
            process.stdout.cursorTo(0);
            process.stdout.clearScreenDown();
        } else {
            toogle = true;
        }

        teachers.forEach(({ name, choosed }, i) => {
            let option = (i === index)
                ? `\x1b[35m> ${name.capitalize()}`
                : name.capitalize();

            if (!!choosed) {
                option = `\x1b[4m${option}`;
            }
            process.stdout.write(`${option}\x1b[0m${i !== teachers.length - 1 ? '\n' : ''}`);
        });
    }

    return new Promise<string[]>((resolve, rejected) => {
        console.log('\x1b[34m?\x1b[0m %s', 'Choose what subjects are taken');
        console.log('\x1b[31m!\x1b[0m %s', 'Press [Esc] to resume process');
        readline.emitKeypressEvents(process.stdin);
        if (process.stdin.isTTY) {
            process.stdin.setRawMode(true);
            process.stdin.resume();
            process.stdin.on('keypress', (_, { name, ctrl }) => {
                if (name === 'down' && index < teachers.length - 1) {
                    ++index;
                } else if (name === 'up' && index > 0) {
                    --index;
                }

                if (name === 'return') {
                    teachers[index].choosed = !teachers[index].choosed;
                }

                if (name === 'escape' || (name === 'c' && ctrl)) {
                    process.stdin.setRawMode(false);
                    process.stdin.pause();

                    if (name === 'c' && ctrl)
                        process.exit(0);

                    process.stdout.moveCursor(0, -(teachers.length + 1));
                    process.stdout.cursorTo(0);
                    process.stdout.clearScreenDown();
                    resolve(teachers.filter(({ choosed }) => choosed).map(({ name }) => name));
                    process.stdin.removeAllListeners('keypress');
                } else {
                    menu();
                }
            });

            menu();
        } else {
            rejected('Idk man, you r ugly');
        }
    });
}

export function selectSchedule(schedules: [any, Map<string, ueaData>][]) {
    let index = 0;
    let toogle = false;
    let clear_area = 4;
    let byDay: weekByDay;
    let schedule_subjects: schedule['subjects_info'];

    const display = () => {
        if (!!toogle) {
            process.stdout.moveCursor(0, -clear_area);
            process.stdout.cursorTo(0);
            process.stdout.clearScreenDown();
            clear_area = 4;
        } else {
            toogle = true;
        }

        const [, schedule] = schedules[index];
        const min_date = Math.min.apply(Math, week.map(day => Math.min.apply(Math, Array.from(schedule)
            .filter(([, { [day]: _ }]) => _)
            .map(([, { [day]: _ }]) => Date.parse('01/01/1970 ' + _?.starts)))));
        const max_date = Math.max.apply(Math, week.map(day => Math.max.apply(Math, Array.from(schedule)
            .filter(([, { [day]: _ }]) => _)
            .map(([, { [day]: _ }]) => Date.parse('01/01/1970 ' + _?.ends)))));

        clear_area += schedule.size;

        schedule_subjects = Array.from(schedule).map(([subject, schedule_info]) => {
            console.log(`${schedule_info.key} | ${schedule_info.teacher.name.capitalize()} | ${subject}`);

            return {
                key: schedule_info.key,
                teacher: schedule_info.teacher.name.capitalize(),
                subject
            };
        });

        byDay = {} as weekByDay;

        for (let i = min_date; i < max_date; i += 1_800_000) {
            const curr_time = timeStringify(i);
            const week_time: perDay = {
                monday: null,
                tuesday: null,
                wednesday: null,
                thursday: null,
                friday: null,
            };

            for (const day of week) {
                for (const [, { key, [day]: _ }] of schedule) {
                    if (_) {
                        if (curr_time >= _.starts && curr_time < _.ends) {
                            week_time[day] = !week_time[day]
                                ? key
                                : key + '/' + week_time[day];
                        }
                    }
                }
            }

            byDay[curr_time] = week_time
            ++clear_area;
        }

        console.table(byDay);
        const left = index !== 0 ? '<' : '';
        const right = index !== schedules.length - 1 ? '>' : '';
        process.stdout.write(`\x1b[34m?\x1b[0m Index ${left}[${index + 1}/${schedules.length}]${right}`);
    }

    return new Promise<schedule>((resolve, reject) => {
        console.log('\x1b[34m?\x1b[0m %s', 'Browse the best schedules posible');
        console.log('\x1b[31m!\x1b[0m %s', 'Press [Enter] to choose schedule');
        readline.emitKeypressEvents(process.stdin);
        if (process.stdin.isTTY) {
            process.stdin.setRawMode(true);
            process.stdin.resume();
            process.stdin.on('keypress', (_, { name, ctrl }) => {
                if (name === 'right' && index < schedules.length - 1) {
                    ++index;
                } else if (name === 'left' && index > 0) {
                    --index;
                }

                if (name === 'return' || (name === 'c' && ctrl)) {
                    process.stdin.setRawMode(false);
                    process.stdin.pause();

                    if (name === 'c' && ctrl)
                        process.exit(0);

                    process.stdout.moveCursor(0, -(clear_area + 2));
                    process.stdout.cursorTo(0);
                    process.stdout.clearScreenDown();
                    resolve({
                        subjects_info: schedule_subjects,
                        hours: byDay
                    });
                    process.stdin.removeAllListeners('keypress');
                } else {
                    display();
                }
            });

            display();
        } else {
            reject('I still dunno man, fkg creep');
        }
    });
}