const { QLog, undefinedOrNull } = require('quanto-commons');
const player = require('node-wav-player');
const TelegramBot = require('node-telegram-bot-api');
const fs = require('fs');
const { exec } = require("child_process");

if(process.platform !== 'darwin') var gpio = require('rpi-gpio');

const telegramBotToken = '5472909337:AAGH269wNGa9u99ekJqHqXEHfi0knpV7IFU';
//const groupid='@HotSoup';
const groupid='-1001703800044';
const GlobalLog = QLog.scope('Global');
const TelLog = QLog.scope('Telegram');

GlobalLog.enableLogs(['debug']);
TelLog.enableLogs(['debug']);

GlobalLog.headPadding = 30;
TelLog.headPadding = 30;

// const bot = new Telegraf(telegramBotToken);
const bot = new TelegramBot(telegramBotToken, {polling: true});

// debounce der buttons in ms
const bounceMS = 800;

// Game Reset Start after ms
const startReset = 4000;

// ist an wenn im kind mode (erw LED is aus)
const childLED = 37;
// ist and wenn im erwachsenen Mode (kind LED ist aus)
const erwLED = 38;
// status : ist an wenn in ruhe position, blinkt, wenn gestartet, ist aus bei fail
const statusLED = 40;
// setze auf HIGH wenn fail (klingel relais)
const failSwitch = 35;

// warte auf spielstart wenn auf HIGH
//const startButton = 32;
//const startButton_GPIO = 12;
// beende spiel wenn auf HIGH
// const finishButton = 31;
const finishButton = 22;
const finishButton_GPIO = 25;
// faile spiel wenn auf HIGH
// const failButton = 29;
const failButton = 21;
const failButton_GPIO = 9;
// wechsle kind/erwachen Modus + LED
//const toggleButton = 33;
//const toggleButton_GPIO = 13;
const startButton = 33;
const startButton_GPIO = 13;

// wird je nach modus gesetzt und abgespielt wenn start
const backgroundGrown = "jaws.wav";
const jeopardy = "jeopardy.wav";
const northsouth = "northsouth.wav";
const backgroundChild = "northsouth.wav";
const backgroundSounds = [ "jaws.wav", "jeopardy.wav", "northsouth.wav" ];

// wird zufallig zu zufaelligen momenten abgespielt (erw mode)
const erschreckSounds = [ "ziege.wav", "scream.wav", "laugh.wav", "cavallerie.wav", "gulp.wav", "ziege.wav" ];
// wird nach fester zeit + random oder spaeter nach der besten zeit abgespielt (erw mode)
// const stresserSound  = "sharks.wav";
// wird bei fail abgespielt (zonk) 
const failSound = "zonk.wav";
// hurra wird bei finish abgespielt
const finishSound = "kidscheering.wav";

const GROWN=1;
const CHILD=2;

let failTimer;
let successTimer;
// LOW EDGE
let preStart = true;

// game running?
let isRunning = false;

const STATES = {
    STARTED: 'started',
    FINISH: 'finshed',
    FAIL: 'failed'
};

let best;

const bounce = {};

var STATE = STATES.FINISH;
var MODE = GROWN;
var GAME = {
    events: [],
    runTime: 0
};

var chatId;

function sendBotGroupImg(chat_id, photo, caption) {
    GlobalLog.info( chat_id, photo);
    bot.sendPhoto(chat_id, photo, {
      caption: caption
    });
}
function sendBotMsg(msg) {
    if(chatId) { 
        bot.sendMessage(chatId, msg);
    }
}

function setup() {

    // bot.start((ctx) => {
    //     let message = `Bot aktiv und beobachtet. Benutze /stats um die Bestenliste zu sehen`;
    //     ctx.reply(message);
    //     TelLog.note('Bot started');
    //     botCTX=ctx;
    // });
    // bot.command('stats', async (ctx) => {
    //        await ctx.replyWithPhoto({ source: '/root/hotwire-js/img/bestgrown.png' });
    //        await ctx.reply(`Bester Erwachsener mit nur ${best.grown} sek`);
    //        await ctx.replyWithPhoto({ source: '/root/hotwire-js/img/bestchild.png' })
    //        await ctx.reply(`Bestes Kind mit nur ${best.child} sek`);
    // });
    // bot.launch();
    fs.readFile('stats.json', 'utf8', (err, res) => {
        if(err) {
            GlobalLog.info("No stats found. Using defaults");
            best = {
                grown: 25,
                child: 37
            }
        } else {
            GlobalLog.info("Stats loaded.");
            best = JSON.parse(res);
        }
    });

    GlobalLog.note('Setting up GPIOs');

    if(process.platform === 'darwin') {
	setTimeout(start, 500);
	setTimeout(fail, 3500);
	setTimeout(start, 4000);
	setTimeout(finish, 9000);
	setTimeout(fail, 10000);
	setTimeout(finish, 11000);
	return;
    }
    gpio.setup(failSwitch, gpio.OUT, () => {
        gpio.output(failSwitch, false);
    });
    gpio.setup(statusLED, gpio.OUT, () => {
        gpio.output(statusLED, true);
    });
    gpio.setup(childLED, gpio.OUT, () => {
        gpio.output(childLED, false);
    });
    gpio.setup(erwLED, gpio.OUT, () => {
        gpio.output(erwLED, true);
    });

    gpio.setup(finishButton, gpio.DIR_IN, gpio.EDGE_FALLING, () => {
        exec(`raspi-gpio set ${finishButton_GPIO} pu`);    
    });
    //gpio.setup(toggleButton, gpio.DIR_IN, gpio.EDGE_FALLING, () => {
    //    exec(`raspi-gpio set ${toggleButton_GPIO} pu`);    
    //});
    gpio.setup(startButton, gpio.DIR_IN, gpio.EDGE_BOTH, () => {
        exec(`raspi-gpio set ${startButton_GPIO} pu`);
    });
    gpio.setup(failButton, gpio.DIR_IN, gpio.EDGE_FALLING, () => {
        exec(`raspi-gpio set ${failButton_GPIO} pu`);    
    });

    gpio.on('change', (channel, value) => {
	if(bounce[channel] === true) return;
	GlobalLog.debug('Change',channel, value);
	bounce[channel] = true;
        setTimeout(() => { bounce[channel] = false; GlobalLog.debug('Debounced', channel); }, bounceMS);
        switch(channel) { 
            case startButton:
                start(value);
                break;
            case finishButton:
                finish(value);
                break;
            case failButton:
                fail(value);
                break;
            //case toggleButton:
            //    toggle(value);
            //    break;
            default:
                GlobalLog.warn('Alert! Unknown button change',channel);
        }
    });
    GlobalLog.note('Done. Game logic started. Waiting for input');
}

function gameTimer() {
    GlobalLog.debug('game timer called');
    GAME.events.forEach((el) => {
        if(el.start === GAME.runTime) {
            GlobalLog.debug('Event :', el);
	    playSound(el.sound);
	}
    });
    GAME.runTime++;
    GAME.led = !GAME.led;
    gpio.output(statusLED, GAME.led);
}

function failTimerFunc() {
    GAME.led = !GAME.led;
    gpio.output(statusLED, GAME.led);
    gpio.output(childLED, GAME.led);
    gpio.output(erwLED, GAME.led);
}
function successTimerFunc() {
    GAME.led = !GAME.led;
    gpio.output(statusLED, GAME.led);
    gpio.output(childLED, !GAME.led);
    gpio.output(erwLED, GAME.led);
}

function getRandomInt(max) {
  return Math.floor(Math.random() * max);
}

function start(value) {
    if(GAME.startplayer) GAME.startplayer.stop();
    if(failTimer) failTimer = clearInterval(failTimer);
    if(successTimer) successTimer = clearInterval(successTimer);
    if(GAME.countTimer) countTimer = clearInterval(GAME.countTimer)

    exec(`killall aplay`);
    if(isRunning === true) {
	if(player) player.stop();
        gpio.output(childLED, false);
        gpio.output(erwLED, true);
	if(GAME.startplayer) GAME.startplayer.stop();
	GlobalLog.note('Game reset.');
    }
    GAME = {
            events: [],
            runTime: 0
    }
    STATE = STATES.FINISH;

    if(STATE === STATES.FINISH || STATE === STATES.FAIL) {
        STATE = STATES.STARTED;
        // set all params for next game, then start counter and event timer
        if(MODE === GROWN) {
            GAME.events.push({ sound: backgroundSounds[getRandomInt(3)], start: 1}); //, start: 15+getRandomInt(10) });
	    // if(getRandomInt(10) > 7) {
            //     GAME.events.push({ sound: jeopardy, start: 1});
	    // } else {
	    //     if(getRandomInt(10) > 7) { 
            //         GAME.events.push({ sound: backgroundGrown, start: 1});
	    //     } else {
            //         GAME.events.push({ sound: northsouth, start: 1});
	    //     }
            // }
            GAME.events.push({ sound: erschreckSounds[getRandomInt(6)], start: 15+getRandomInt(10) });
            GAME.events.push({ sound: erschreckSounds[getRandomInt(6)], start: 30+getRandomInt(10) });
            GAME.events.push({ sound: erschreckSounds[getRandomInt(6)], start: 50+getRandomInt(10) });
            GAME.events.push({ sound: erschreckSounds[getRandomInt(6)], start: 70+getRandomInt(10) });
            GAME.mode = GROWN;
            GAME.fail = failSound;
            GAME.finish = finishSound;
            GAME.runtime = 0;
        }
        if(MODE === CHILD) {
            GAME.events.push({ sound: backgroundChild, start: 0});
            GAME.events.push({ sound: erschreckSounds[getRandomInt(2)], start: 30+getRandomInt(10)});
            GAME.mode = CHILD;
            GAME.fail = failSound;
            GAME.finish = finishSound;
            GAME.runtime = 0;
        }
    	GlobalLog.note('Starting new game', JSON.stringify(GAME, 2, null));
	isRunning = true;
        GAME.countTimer = setInterval(gameTimer, 1000);
	sendBotMsg('new game startet');
	
    } else {
        GlobalLog.warn('START not in state', STATE);
    }
}

function finish(value) {
    GlobalLog.debug('Finish');
    if(STATE === STATES.STARTED) {
        GlobalLog.note('You made it in', GAME.runTime, 'seconds');
        GlobalLog.note(GAME);
        player.stop();
	playSound(GAME.finish);
        STATE = STATES.FINISH;
	clearInterval(GAME.countTimer)
        successTimer = setInterval(successTimerFunc, 800);
	GlobalLog.note('Getting new image!')
        exec(`fswebcam -r 1024x768 /tmp/last.png`, (err) => {
	    let rt = GAME.runTime;
	    if(err) {
                GlobalLog.error('Could not get image', err);
	    }
	    GAME.events = [];
            var msg;
	    var img;
	    if(MODE === GROWN) {
                if(GAME.runTime < best.grown) {
	    	best.grown = rt; 
	            msg = `Gewinner in nur ${rt} Sekunden!\nNeue Bestzeit!`;
		    fs.copyFile('/tmp/last.png', '/tmp/bestgrown.png', (err) => {});
	        } else {
	            msg = `Gewinner in nur ${rt} Sekunden!`;
	        }
	    } else {
                if(GAME.runTime < best.child) {
	            sendBotMsg(`Neue Bestzeit bei den Kindern!`);
	    	    best.child = rt;
	            msg = `Gewinner in nur ${rt} Sekunden!\nNeue Bestzeit bei den Kindern!`;
		    fs.copyFile('/tmp/last.png', '/tmp/bestchild.png', (err) => {});
	        } else {
	            msg = `Gewinner in nur ${rt} Sekunden!`;
                }
	    }
            sendBotGroupImg(groupid, '/tmp/last.png', msg);
	    GAME.led = true;
	});
	preStart = false;
	isRunning = false;
    } else {
        GlobalLog.warn('FINISH not in state', STATE);
    }
    // exec(`killall aplay`);
}

function fail(value) {
    if(STATE === STATES.STARTED) {
        GlobalLog.note('You failed after', GAME.runTime, 'seconds');
        if(GAME.startplayer) GAME.startplayer.stop();
        if(player) player.stop();
	playSound(GAME.fail);
        STATE = STATES.FAIL;
	clearInterval(GAME.countTimer)
        failTimer = setInterval(failTimerFunc, 100);
        GAME.events = [];
	preStart = false;
	isRunning = false;
        exec(`sleep 2; killall aplay`);
    } else {
        GlobalLog.debug('FAIL not in state', STATE);
    }
}

function toggle(value) {
    if(STATE === STATES.FINISH || STATE === STATES.FAIL) {
        if(failTimer) clearInterval(failTimer);
        if(successTimer) clearInterval(successTimer);
        if(MODE === GROWN) {
            gpio.output(erwLED, true);
        } else {
            gpio.output(childLED, true);
        }
        GlobalLog.info('Toggle, old', MODE);
        if(MODE === CHILD) { 
            MODE = GROWN;
            gpio.output(childLED, false);
            gpio.output(erwLED, true);
	    // set all values for GROWN mode
        } else {
            MODE = CHILD;
            gpio.output(childLED, true);
            gpio.output(erwLED, false);
	    // set all values for CHILD mode
        }
    } else {
        GlobalLog.debug('Toggle not in state', STATE);
    }
}

function playSound(file, runTime){
    if(!file) { GlobalLog.error('Sound not defined.'); return; }
    GlobalLog.info("Play sound : ", file);
    if(GAME.playing) {
       GlobalLog.debug("Stopping old soundfile", GAME.playing);
       player.stop();
    }
    // Do not stop background sound
    if(GAME.runTime > 1) {
        GAME.playing = file;
    } else {
	GAME.startplayer = player;
    }
    player.play({ path: `wav/${file}`, sync: true }).then(() => { delete GAME.playing })
}

setup();

TelLog.start('Starting bot...');
bot.on('message', function (msg) {
    const chatId = msg.chat.id;
    const resp = msg;
    TelLog.info(`${chatId}: ${resp.text}`);
    if(resp.text === '/stats') {
        TelLog.info(`Dump stats to channel ${groupid}`);
        if (fs.existsSync('/tmp/bestchild.png')) {
            sendBotGroupImg(groupid, '/tmp/bestchild.png', `Bestes Kind mit ${best.child} Sekunden.`);
        }
        if (fs.existsSync('/tmp/bestgrown.png')) {
            sendBotGroupImg(groupid, '/tmp/bestgrown.png', `Bester Erwachsener mit ${best.grown} Sekunden.`);
        }
    }
});


