// requirements
const Discord = require('discord.js');
const auth = require('./auth.json');
const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
const fetch = require("node-fetch-polyfill");

// voice setup
var playQueue = [];
var playVolume = [];
var queueExists = false;
var channel = null;

// grab config
var bot_config = asyncSiteRequest('http://127.0.0.1/folder/bot-config.json', config_data => {
    return config_data;
});

// available commands
// todo use config file
function availableCommands() {
    console.log('availaleCommands called.');
    return "Available commands: " +
        "\n**[SFW]** b.<*coinflip, 8ball, roll, compute, summon, bunny, dankmemes, meirl, animemes, moe, moe-boys, hotguys , kgirls, kboys*>" +
        "\n**[NSFW]** b.<*lewd, ecchi, hentai, yaoi, yuri, neko, pokeporn, hgifs*>" +
        "\nFor more information on a command, you may use: *b.help <command>*";
}

// help commands
// todo use config file to grab botConfig.commandHelp
function commandsHelp(requestedCommand) {
    console.log('commandsHelp called.');
    let commands = {
        "coinflip": "**coinflip:** Returns a result of heads or tails.",
        "8ball": "**8ball:** Shakes an 8 ball and returns a result.",
        "roll": "**roll:** Rolls a number between 1-100 (or a specified range [ex: b.roll 50-100])",
        "compute": "**compute:** Uses WolframAlpha to process the question asked.",
        "summon": "**summon:** Posts a picture from a user inputted subreddit",
        "bunny": "**bunny:** Posts a picture of different bunnies",
        "dankmemes": "**dankmemes:** Posts a picture of some dank memes (r/dankmemes)",
        "meirl": "**meirl:** Posts a picture of anime irl (r/anime_irl)",
        "animemes": "**animemes:** Posts a picture of anime memes (r/animemes)",
        "moe": "**moe:** Posts a picture of moe anime (r/awwnime)",
        "moe-boys": "**moe-boys:** Posts a picture of moe anime boys (r/cuteanimeboys)",
        "hotguys": "**hotguys:** Posts a picture of hot anime guys (r/bishounen)",
        "kgirls": "**kgirls:** Posts a picture of k-pop girls (r/kpics or r/kpopfap)",
        "kboys": "**kboys:** Posts a picture of k-pop guys (r/kfanservice, r/kpecs, or r/cutekboys)",
        "lewd": "[NSFW] **lewd:** Posts a picture of lewd anime girls (r/pantsu)",
        "ecchi": "[NSFW] **ecchi:** Posts a picture of ecchi anime girls (r/ecchi)",
        "hentai": "[NSFW] **hentai:** Posts a picture of hentai (r/hentai or r/sukebei)",
        "yaoi": "[NSFW] **yaoi:** Posts a picture of yaoi (r/yaoi)",
        "yuru": "[NSFW] **yuri:** Post a picture of yuri (r/yuri)",
        "neko": "[NSFW] **neko:** Posts a picture of cat girls (r/nekomimi)",
        "pokeporn": "[NSFW] **pokeporn:** Posts a picture of pokemon hentai (r/pokeporn)",
        "hgifs": "[NSFW] **hgifs:** Posts a gif of hentai (r/nsfwanimegifs)",
        "hentaibondage": "[NSFW] **hentaibondage:** Posts a picture of hentai in bondage (r/hentaibondage)"
    };

    if (commands[requestedCommand] === undefined) {
        console.log('requestedCommand not found in map.');
        return "Command not found, try entering **b.commands** for a full list of commands";
    } else {
        console.log('requestedCommand found, returning requestedCommand map entry.');
        return commands[requestedCommand];
    }
}

// For now abstracting this, so we can maybe use config later.
// content should be a string or something js thinks is a string :)
function sendToBotTestingChannel(content) {
    console.log(content);
    client.channels.get('357365312620068874').send(content);
}

// coin flip
function coinFlip() {
    console.log('coinFlip called.');
    return (Math.floor(Math.random() * 2) === 0) ? 'heads' : 'tails';
}

// 8 ball
function eightBall() {
    console.log('eightBall called.');
    let answers = [
        'Maybe.', 'Certainly not.', 'I hope so.', 'Not in your wildest dreams.',
        'There is a good chance.', 'Quite likely.', 'I think so.', 'I hope not.',
        'I hope so.', 'Never!', 'Fuhgeddaboudit.', 'Ahaha! Really?!?', 'Pfft.',
        'Sorry, bucko.', 'Hell, yes.', 'Hell to the no.', 'The future is bleak.',
        'The future is uncertain.', 'I would rather not say.', 'Who cares?',
        'Possibly.', 'Never, ever, ever.', 'There is a small chance.', 'Yes!'];

    return answers[(Math.floor(Math.random() * answers.length))];
}

// number roll
function numberRoll(startingRange, endingRange) {
    startingRange = Math.ceil(startingRange);
    endingRange = Math.floor(endingRange);

    console.log('numberRoll called. rolling a number inbetween ' + startingRange + ' and ' + endingRange);
    return Math.floor(Math.random() * (endingRange - startingRange)) + startingRange;
}

// stupid question response
function stupidQuestion() {
    console.log('someone asked a stupid question.');
    let answers = [
        'What kind of dumb question is that?', 'A real question please.',
        'Is that supposed to be a question?', 'Who asked such a dumb question?',
        'How about a good question instead?', 'Only good questions please',
        'Oh god, is that a question?', 'Are you dumb because that\'s a dumb question.'];

    return answers[(Math.floor(Math.random() * answers.length))];
}

function noQuestion() {
    console.log('no question was asked.');
    let answers = ['it helps to ask a question...', 'question plz.', 'where is the question?'];

    return answers[(Math.floor(Math.random() * answers.length))];
}

// Try's all sources and returns in preferred order
// 1. Reddit
// 2. Imgur
// 3. RedditBooru
function bestEffortRequest(subpage, page_max) {

    let reddit_url = "https://www.reddit.com/r/" + subpage + "/.json?show=all&count=25&limit=100";
    let reddit_response = asyncSiteRequest(reddit_url, handleRedditJson);
    if (reddit_response) {
        return reddit_response;
    }

    let imgur_url = 'https://api.imgur.com/3/gallery/r/' + subpage + '/time/' + getRandomInt(1, page_max);
    let imgur_response;
    if (page_max) {
        imgur_response = asyncSiteRequest(imgur_url, handleImgurJson);
        if (imgur_response) {
            return imgur_response
        }
    }

    let redditbooru_response = redditbooruRequest(subpage);
    if (redditbooru_response) {
        return redditbooru_response
    }

    // leaving this here for debug.
    sendToBotTestingChannel('reddit:' + reddit_response + '\nimgur:' + imgur_response + '\nredditbooru:' + redditbooru_response);

    return "I couldn't find that, sauce?";
}

// imgur request
function imgurRequest(subreddit, page_max) {
    let multiSubreddit = subreddit.split(',');

    let request_url = 'https://api.imgur.com/3/gallery/r/' + multiSubreddit[getRandomInt(0, multiSubreddit.length - 1)] + '/time/' + getRandomInt(1, page_max);

    let req = new XMLHttpRequest();
    let returnText = "";

    req.onreadystatechange = function () {
        if (req.readyState === 4 && req.status === 200) {
            if (req.responseText !== "Not found") {
                try {
                    let json = JSON.parse(req.responseText);
                    let json_data = json.data[getRandomInt(0, json.data.length - 1)];
                    if (json_data) {
                        returnText = json_data.link;
                    }
                } catch (error) {
                    // Ignore the error and let the calling method handle the empty string.
                    sendToBotTestingChannel(error.message);
                }
            }
        }
    };

    req.open("GET", request_url, false);
    req.setRequestHeader('Authorization', 'Client-ID ' + auth.imgur);
    req.send();

    return returnText;
}

// Abstracted Reddit Json media grab.
function handleRedditJson(data) {
    let json = data.json();
    for (let i = 0; i < json.data.children.length; i++) {
        let rand = getRandomInt(0, json.data.children.length - 1);

        if (json.data.children[rand].data.post_hint === "image" || json.data.children[rand].data.post_hint === "link" || json.data.children.data.post_hint === "rich:video") {
            return json.data.children[rand].data.url;
        }
    }
    // What if we don't find anything i.e. r/dogs but its valid subreddit.
    return "";
}

// Abstracted Imgur Json media grab.
function handleImgurJson(data) {

    let json = data.json();
    let json_data = json.data[getRandomInt(0, json.data.length - 1)];
    if (json_data) {
        return json_data.link;
    }
}

// Makes an async url request with the provided function: 'process'.
function asyncSiteRequest(url, process) {

    fetch(url)
        .then(data => {
            return process(data);
        })
        .catch(error => {
            sendToBotTestingChannel(error.message);
            return "";
        })
}

// subreddit request
function subredditRequest(subreddit) {
    let multiSubreddit = subreddit.split(',');

    let redditURL = "https://www.reddit.com/r/" + multiSubreddit[getRandomInt(0, multiSubreddit.length - 1)] + "/.json?show=all&count=25&limit=100";
    let req = new XMLHttpRequest();
    let returnText = "";

    req.onreadystatechange = function () {
        if (req.readyState === 4 && req.status === 200) {

            let json = JSON.parse(req.responseText);

            let counter = 0;

            while (true) {
                let i = getRandomInt(0, json.data.children.length - 1);
                try {

                    counter++;

                    if (json.data.children[i].data.post_hint === "image" || json.data.children[i].data.post_hint === "link" || json.data.children.data.post_hint === "rich:video") {
                        returnText = json.data.children[i].data.url;
                        break;
                    }
                } catch (error) // error parsing json
                {
                    // Ignore the error and continue.
                    // continue; <-- last line in the loop... so does nothing.
                    sendToBotTestingChannel(error.message);
                }

                if (counter > 5) {
                    break;
                }
            }
        }
    };

    req.open("GET", redditURL, false);
    req.send();

    return returnText;
}

// redditbooru request
function redditbooruRequest(subreddit) {
    let multiSubreddit = subreddit.split(',');

    let url = "https://" + multiSubreddit[getRandomInt(0, multiSubreddit.length - 1)] + ".redditbooru.com/images/?limit=1000";
    let req = new XMLHttpRequest();
    let returnText = "";

    req.onreadystatechange = function () {
        if (req.readyState === 4 && req.status === 200) {
            let json = JSON.parse(req.responseText);
            let imageID = getRandomInt(0, json.length);
            returnText = json[imageID].cdnUrl;
        }
    };

    req.open("GET", url, false);
    req.send();

    return returnText;
}

// wolframalpha computation
function calculate(message) {
    let url = "http://api.wolframalpha.com/v1/result?appid=" + auth.wolframalpha + "&i=" + encodeURIComponent(message);
    let req = new XMLHttpRequest();
    let returnText = "";

    req.onreadystatechange = function () {
        if (req.readyState === 4 && req.status === 200) {
            returnText = req.responseText;
        }
    };

    req.open("GET", url, false);
    req.send();

    return returnText;
}

// get random number with starting and ending number
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

// voice setup
function setupVoice(file, volume, voice_channel) {
    playQueue.unshift(file);
    playVolume.unshift(volume);

    if (!channel) {
        channel = voice_channel;
    }
    if (channel instanceof Discord.VoiceChannel) {
        if (!queueExists) {
            queueExists = true;
            channel.join().then(connection => {
                handlePlayQueue(connection);
            });
        }
    } else {
        playQueue.pop();
        playVolume.pop();
        channel = null;
    }
}

// voice queue
function handlePlayQueue(connection) {
    if (playQueue.length === 0) {
        return;
    }

    let currentCommand = playQueue[0];
    const dispatcher = connection.playFile(currentCommand);
    dispatcher.on("end", () => {
        playQueue.pop();
        playVolume.pop();

        if (playQueue.length === 0) {
            queueExists = false;
            if (channel != null) {
                channel.leave();
                channel = null;
            }
        } else {
            handlePlayQueue(connection);
        }
    })
    ;
    dispatcher.setVolume(playVolume[0]);
}

// discord setup
const client = new Discord.Client();

client.on('ready', () => {

    console.log('Bot is ready');

    // update status
    let availableTypes = ['PLAYING', 'LISTENING', 'WATCHING'];
    let setType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
    let setStatus;
    let availableStatus;

    switch (setType) {
        case 'PLAYING':
            availableStatus = ['with cute bunnies', 'with bunny toys', 'in bunny heaven', 'with my pet bunny', 'with all the bunnies'];
            break;
        case 'LISTENING':
            availableStatus = ['to cute bunnies', 'to adorable bunnies eating', 'to bunnies playing with toys', 'to bunnies pouting'];
            break;
        case 'WATCHING':
            availableStatus = ['cute bunnies', 'cute bunnies play', 'cute bunnies sleep', 'bunnies nibbling on food', 'swiftimustv'];
            break;
    }
    setStatus = availableStatus[Math.floor(Math.random() * availableStatus.length)];

    // todo this set activity returns a promise and we aren't doing anything with it
    client.user.setActivity(setStatus, {type: setType})
        .catch(error => {
            sendToBotTestingChannel(error.message);
            console.log(error.message);
        });
})
;

// commands
client.on('message', message => {
    if (message.content.substring(0, 2).toUpperCase() === 'B.' || message.content.substring(0, 2) === '//') {
        let args = message.content.substring(2).split(' ');
        let cmd = args[0];

        switch (cmd) {
            // Reply commands
            case 'command':
            case 'commands':
            case 'help':
                let help_content = message.content.substring(2).split(' ');
                if (message.content.substring(2).split(' ')[1] === undefined) {
                    message.channel.send(availableCommands());
                } else {
                    message.channel.send(commandsHelp(help_content[1]));
                }
                break;
            case 'coinflip':
            case 'coin':
                message.channel.send(message.author + ' flipped a coin, it landed on **' + coinFlip() + '!**');
                break;
            case '8ball':
                if (message.content.substring(8).length > 0) {
                    message.channel.send(message.author + ' asked: *' + message.content.substring(8) + '*\n' +
                        'The magic 8 ball says: **' + eightBall() + '**');
                } else {
                    message.reply(noQuestion());
                }
                break;
            case 'roll':
                let roll_content = message.content.substring(2).split(' ');
                if (roll_content[1] !== undefined && roll_content[1].length > 0) // ranged roll
                {
                    let regex = /^[0-9]+-[0-9]+$/;
                    if (regex.test(roll_content[1])) {
                        let range_break = roll_content[1].split('-');

                        message.channel.send(message.author + ' rolls a number between ' + range_break[0] + ' and ' + range_break[1] + '. They roll **' + numberRoll(range_break[0], range_break[1]) + '**.');
                    } else {
                        message.channel.send('Unknown roll format. Use "**b.help roll**" for more information.');
                    }
                } else // default roll (1-100)
                {
                    message.channel.send(message.author + ' rolls a number between 1 and 100. They roll **' + numberRoll(1, 100) + '**.');
                }
                break;
            case 'compute':
            case 'convert':
            case 'calculate':
                let message_content = message.content.substring(2).split(' ');
                if (message_content[1] !== undefined && message_content[1].length > 0) {
                    let response = calculate(message.content.substring(message.content.indexOf(' ') + 1));
                    if (response === "") {
                        response = stupidQuestion();
                    }
                    message.channel.send(message.author + ' wants to compute: *' + message.content.substring(message.content.indexOf(' ') + 1) + '*\n' +
                        'The result is: **' + response + '**')
                } else {
                    message.reply(noQuestion());
                }
                break;

            // SFW Generic Image commands
            case 'bun':
            case 'bunny':
            case 'bunnies':
                message.channel.send(imgurRequest('rabbits', 5));
                break;
            case 'dankmeme':
            case 'dankmemes':
                message.channel.send((Math.floor(Math.random() * 2) === 0) ? subredditRequest('dankmemes') : imgurRequest('dankmemes', 5));
                break;
            case 'source':
                message.channel.send("BunnyBot's source code: https://git.dtam.pw/daniel/discord-bot-js");
                break;
            case 'testcommand':
                message.channel.send("testing! 3.0");
                break;


            // SFW Anime Image commands
            case 'meirl':
                message.channel.send((Math.floor(Math.random() * 2) === 0) ? subredditRequest('anime_irl') : imgurRequest('anime_irl', 5));
                break;
            case 'animemes':
                message.channel.send((Math.floor(Math.random() * 2) === 0) ? subredditRequest('animemes') : imgurRequest('animemes', 5));
                break;
            case 'moe':
                message.channel.send((Math.floor(Math.random() * 2) === 0) ? redditbooruRequest('awwnime') : imgurRequest('awwnime', 5));
                break;
            case 'moe-boys':
                message.channel.send((Math.floor(Math.random() * 2) === 0) ? subredditRequest('cuteanimeboys') : imgurRequest('cuteanimeboys', 1));
                break;
            case 'hotguys':
                message.channel.send((Math.floor(Math.random() * 2) === 0) ? redditbooruRequest('bishounen') : subredditRequest('bishounen'));
                break;
            case 'kgirls':
                switch (Math.floor(Math.random() * 3)) {
                    case 0:
                        message.channel.send(redditbooruRequest('kpics'));
                        break;
                    case 1:
                        message.channel.send(imgurRequest('kpics', 5));
                        break;
                    case 2:
                        message.channel.send(subredditRequest('kpopfap'));
                        break;
                }
                break;
            case 'kboys':
                switch (Math.floor(Math.random() * 3)) {
                    case 0:
                        message.channel.send(imgurRequest('kfanservice', 1));
                        break;
                    case 1:
                        message.channel.send(imgurRequest('kpecs', 1));
                        break;
                    case 2:
                        message.channel.send(imgurRequest('cutekboys', 1));
                        break;
                }
                break;

            // NSFW
            case 'lewd':
                message.channel.send(redditbooruRequest('pantsu'));
                break;
            case 'ecchi':
                switch (Math.floor(Math.random() * 4)) {
                    case 0:
                        message.channel.send(subredditRequest('ecchi'));
                        break;
                    case 1:
                        message.channel.send(redditbooruRequest('ecchi'));
                        break;
                    case 2:
                        message.channel.send(redditbooruRequest('Sukebei'));
                        break;
                    case 3:
                        message.channel.send(imgurRequest('ecchi', 5));
                        break;
                }
                break;
            case 'hentai':
                switch (Math.floor(Math.random() * 3)) {
                    case 0:
                        message.channel.send(subredditRequest('hentai', 5));
                        break;
                    case 1:
                        message.channel.send(subredditRequest('hentaisource', 5));
                        break;
                    case 2:
                        message.channel.send(imgurRequest('hentai', 5));
                        break;
                }
                break;
            case 'yaoi':
                message.channel.send(imgurRequest('yaoi', 5));
                break;
            case 'yuri':
                message.channel.send(imgurRequest('yuri', 5));
                break;
            case 'neko':
                message.channel.send((Math.floor(Math.random() * 2) === 0) ? imgurRequest('Nekomimi', 5) : subredditRequest('Nekomimi', 5));
                break;
            case 'pokeporn':
                message.channel.send((Math.floor(Math.random() * 2) === 0) ? subredditRequest('pokeporn') : redditbooruRequest('pokeporn'));
                break;
            case 'hgifs':
                message.channel.send(subredditRequest('nsfwanimegifs', 5));
                break;
            case 'hentaibondage':
                message.channel.send((Math.floor(Math.random() * 2) === 0) ? subredditRequest('hentaibondage') : imgurRequest('hentaibondage', 5));
                break;

            case 'myid':
                message.reply('Author is:' + message.author + '\nID is:' + message.author.id);
                break;
            case 'summon':
                try {
                    // Dalton's image summon command.
                    // Checks for banned author's using a hard coded list of discord ids.
                    // Also rudely cleans garbage entries.
                    let banned_authors = ['143999140878090240'];
                    if (!banned_authors.includes(message.author.id)) {

                        let messageSplit = message.content.substring(2).split(' ');
                        if (messageSplit.length >= 2) {
                            let redditToSummon = messageSplit[1].replace(/[^a-zA-Z0-9_\-]+/g, '');

                            // Valid Argument
                            if (redditToSummon) {

                                message.channel.send(message.author + ' summons ' + redditToSummon + '\n');
                                let site_response = bestEffortRequest(redditToSummon, 5);

                                if (site_response) {
                                    message.channel.send(site_response);
                                }

                                // Missing Argument
                            } else {
                                message.channel.send('wut do i summon?');
                            }
                        }
                    } else {
                        message.channel.send("You're a bad boi and a banned author, you can't summon til you learn to behave.");
                    }
                } catch (error) {
                    //message.channel.send(error.stack);
                    //message.channel.send(error.name);
                    //message.channel.send(error.message);
                    sendToBotTestingChannel(error.message);
                }
                break;

            // Voice commands
            case 'join':
                channel = message.member.voiceChannel;
                channel.join();
                break;
            case 'leave':
            case 'stop':
            case 'skip':
                if (channel != null) {
                    channel.leave();
                    channel = null;
                }
                playQueue = [];
                break;
            case 'airhorn':
                setupVoice('voice/mlg-airhorn.mp3', 0.4, message.member.voiceChannel);
                break;
            case 'quiethorn':
                setupVoice('voice/mlg-airhorn.mp3', 0.03, message.member.voiceChannel);
                break;
            case 'weed':
                setupVoice('voice/smoke-weed.mp3', 0.2, message.member.voiceChannel);
                break;
            case 'damnson':
                setupVoice('voice/damnson.mp3', 0.35, message.member.voiceChannel);
                break;
            case 'wombo':
                setupVoice('voice/wombocombo.mp3', 0.06, message.member.voiceChannel);
                break;
            case 'cena':
                setupVoice('voice/cena.mp3', 0.10, message.member.voiceChannel);
                break;
            case 'triple':
                setupVoice('voice/triple.mp3', 0.20, message.member.voiceChannel);
                break;
        }
    }
})
;

// login
client.login(auth.token)
    .catch(error => console.log(error));
