//#ifdef HAVE_CONFIG_H
#include <config.hpp>
//#endif

#include "mc-hid-server.hpp"
#include "log/log.hpp"

#include <pigpio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <limits.h>
#include <pthread.h>

using namespace std;

const uint8_t BTN_HOME          = 7;
const uint8_t BTN_CANCEL        = 8;
const uint8_t BTN_UP            = 9;
const uint8_t BTN_DOWN          = 10;
const uint8_t BTN_PLAY          = 11;
const uint8_t BTN_FWD           = 12;
const uint8_t BTN_RWD           = 16;
const uint8_t BTN_OK            = 17;

// map the buttons to names
const std::map<uint8_t,std::string> BUTTONS = {
    { BTN_HOME,     "BTN_ESCAPE"},
    { BTN_CANCEL,   "BTN_CANCEL"},
    { BTN_UP,       "BTN_UP"},
    { BTN_DOWN,     "BTN_DOWN"},
    { BTN_FWD,      "BTN_FASTFORWARD"},
    { BTN_PLAY,     "BTN_PLAYPAUSE"},
    { BTN_RWD,      "BTN_REWIND"},
    { BTN_OK,       "BTN_OK"},
};

// define the active state of the 
const std::map<uint8_t,bool> BUTTONS_ACTIVE = {
    { BTN_HOME,     true},
    { BTN_CANCEL,   false},
    { BTN_UP,       false},
    { BTN_DOWN,     false},
    { BTN_FWD,      false},
    { BTN_PLAY,     false},
    { BTN_RWD,      false},
    { BTN_OK,       false},
};

const std::vector<uint8_t> BUTTONS_REGULAR = {
    BTN_CANCEL,
    BTN_UP,
    BTN_DOWN,
    BTN_FWD,
    BTN_PLAY,
    BTN_RWD,
    BTN_OK,
};


const uint8_t LED_RED       = 4;
const uint8_t LED_GREEN     = 5;
const uint8_t LED_BLUE      = 6;

const uint8_t FAN_CTL       = 13;

const int PULSE_STEPS       = 32;



static const char *HID_SERVER_NAME = "nl.miqra.MediaCore.Hid";
static const char *HID_SERVER_PATH = "/nl/miqra/MediaCore/Hid";



// signal handler
void niam(int sig);

HidServer::HidServer(DBus::Connection &connection)
  : DBus::ObjectAdaptor(connection, HID_SERVER_PATH)
{

    // Initialize button timer
    btnTimer = new ButtonTimer(10,5000); // Short press should take at leas 10 ms, and a Long press takes 5 seconds
    onShortPressConnection      = this->btnTimer->onShortPress.connect(     boost::bind( &HidServer::onShortPress, this, _1)     );
    onLongPressConnection       = this->btnTimer->onLongPress.connect(      boost::bind( &HidServer::onLongPress, this, _1)      );
    onValidatePressConnection   = this->btnTimer->onValidatePress.connect(  boost::bind( &HidServer::onValidatePress, this, _1)  );

    // **** Initialize the GPIO Interrupt pin
    
    clog << kLogInfo << PACKAGE_STRING << " starting..." << endl;
    
    try
    {
        // Setup all buttons for pullup and input
    
        // Setup home button (which is active-high)
        clog << kLogInfo << "Connecting to Home button";
        this->btHome = new GpioPin(BTN_HOME);
        clog << kLogInfo << ".";
        onBtHomeConnection      = this->btHome->onChange.connect( boost::bind( &HidServer::onBtChange, this, _1, _2, _3) );
        clog << kLogInfo << ".";
        this->btHome->Listen(GpioEdge::Both, GpioPullup::PullUp, false);
        clog << kLogInfo << "." << endl;

        // Setup theo ther butons (which are active-low)
        clog << kLogInfo << "Connecting to other buttons";
        this->btOther = new GpioPins(BUTTONS_REGULAR);
        clog << kLogInfo << ".";
        onBtOtherConnection     = this->btOther->onChange.connect( boost::bind( &HidServer::onBtChange, this, _1, _2, _3) );
        clog << kLogInfo << ".";
        this->btOther->Listen(GpioEdge::Both, GpioPullup::PullUp, true);
        clog << kLogInfo << "." << endl;

        // setup the LED
        this->rgbLed = new RgbLed(LED_RED,LED_GREEN,LED_BLUE);
    }
    catch(const std::exception& x)
    {
        clog << kLogCrit << endl << "Fatal error during hardware initialization: " << x.what() << endl << "Quitting now... " << endl;
        exit(-1);
    }

    clog << kLogInfo << "Initialization complete, listening to HID requests." << endl; 
}

HidServer::~HidServer()
{
    ClearColor();

    onBtHomeConnection.disconnect();
    onBtOtherConnection.disconnect();
    onShortPressConnection.disconnect();

    onLongPressConnection.disconnect();
    onValidatePressConnection.disconnect();

    delete this->btnTimer; this->btnTimer = NULL;
    delete this->btOther;
    delete this->btHome;
    delete this->rgbLed;

    clog << kLogInfo << "Stopping normally" << endl;
}


void HidServer::SetColor(const uint8_t& r, const uint8_t& g, const uint8_t& b)
{
    clog << kLogDebug << "Got color request: RGB("<< (int)r << "," << (int)g << "," << (int)b << ") - " << endl;
    this->ThreadStop();
    this->rgbLed->SetColor(r,g,b);
}

void HidServer::PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval)
{
	pulseR = r;
	pulseG = g;
	pulseB = b;

	pulseDirUp = true;
	pulseMin = 0;
	pulseI = pulseMin;
	
    clog << kLogDebug << "Got pulse color request: RGB("<< (int)r << "," << (int)g << "," << (int)b << ")" << endl;
    
    // Default PULSE_STEPS is 32, reduce accordingly when interval is below 2 seconds
    // with 32 pulse steps, this results in min 2.0 / (2*32) =  31 ms per color
    pulseSteps = PULSE_STEPS;
    if(interval > 2.0)
    {
        pulseSteps = (int32_t)(interval * pulseSteps);
    }

    pulseTime = (int32_t)((interval * 1000000) / (2 * pulseSteps));
	// Start pulse thread and PWM system
	ThreadStart();

}

// contains the pulse loop (note: faster pulsing does use more cpu)
void HidServer::ThreadLoop()
{
	uint8_t myR, myG, myB;
	int32_t factor = pulseI*100;

	
	myR = (pulseR * factor)/(100 * pulseSteps);
	myG = (pulseG * factor)/(100 * pulseSteps);
	myB = (pulseB * factor)/(100 * pulseSteps);
	
    this->rgbLed->SetColor(myR,myG,myB);
	
	if(pulseDirUp)
	{
		pulseI++;
		if(pulseI>= pulseSteps)
			pulseDirUp = false;
	}
	else
	{
		pulseI--;
		if(pulseI <= pulseMin)
			pulseDirUp = true;
	}
	
	usleep(pulseTime);
		
}

void HidServer::ClearColor()
{
    clog << kLogDebug << "Clearing color" << endl;
	// Stop pulse thread 
	ThreadStop();

    // Disable PWM and turn all colors off
    this->rgbLed->SetColor(0,0,0);
}

void HidServer::onBtChange(uint8_t gpio, GpioEdge edge, bool level)
{
    if(edge == GpioEdge::Rising){
        this->btnTimer->RegisterPress((uint8_t)gpio);
//        clog << kLogDebug << "    (Button down : " << getBtnName((uint8_t)gpio) << ")" << endl;
    }
    else 
    {
        this->btnTimer->RegisterRelease((uint8_t)gpio);
//        clog << kLogDebug << "    (Button up   : " << getBtnName((uint8_t)gpio) << ")" << endl;
    }
}


bool HidServer::onValidatePress(uint8_t gpio)
{
    // Check if the key is still pressed before sending out a long press
    return true; //((bool)gpioRead(gpio)) == BUTTONS_ACTIVE.at(gpio);
}

void HidServer::onShortPress(uint8_t gpio)
{
    clog << kLogDebug << "SHORT PRESS : " << getBtnName(gpio) << endl;
    ButtonPress(getBtnName(gpio));
}

void HidServer::onLongPress(uint8_t gpio)
{
    clog << kLogDebug << "LONG PRESS  : " << getBtnName(gpio) << endl;
    ButtonLongPress(getBtnName(gpio));
}

// Convert gpio into a string for ease of use in e.g. python or other dbus bindings
std::string HidServer::getBtnName(uint8_t gpio)
{
   return BUTTONS.at(gpio);
}


DBus::BusDispatcher dispatcher;

void niam(int sig)
{
  dispatcher.leave();
}

int main()
{
    signal(SIGTERM, niam);
    signal(SIGINT, niam);
    signal(2,niam);

    DBus::default_dispatcher = &dispatcher;

    // Initialize clog to be redirected to syslog key "mediacore.hid.server"
//    Log::Init("mediacore.hid");

    DBus::Connection conn = DBus::Connection::SystemBus();
    conn.request_name(HID_SERVER_NAME);

    //gpioCfgClock(10,1,0);
    //gpioInitialise();

    HidServer server(conn);


    dispatcher.enter();

    //gpioTerminate();
    return 0;
}