#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <lmdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include <poll.h>
#include <signal.h>

#include <logger.h>
#include <models/controller.h>
#include <database.h>
#include <config.h>
#include <handlers.h>
#include <drivers.h>
#include <enums.h>
#include <helpers.h>
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>

static MDB_env *mdb_env;
static int fd_discovery;
static int fd_command;
static controller_t *this_controller;

static void
terminate(int signum)
{
    LOG_INFO("terminating controller (%d)", signum);

    close(fd_discovery);
    close(fd_command);

    mdb_env_close(mdb_env);

    controller_free(this_controller);

    exit(signum);
}

static void
handle_poll(struct pollfd *fds, int fd_count)
{
    /* An event on one of the fds has occurred. */
    for(int i = 0; i < fd_count; i++) {
        if(fds[i].revents & POLLIN)
        {
            /* data may be read on device number i. */
            LOG_DEBUG("fd %i may read data", fds[i].fd);
            switch(i)
            {
                case POLL_FGS_DISCOVERY:
                    handler_discovery(fd_discovery, this_controller);
                    break;
                case POLL_FGS_COMMAND:
                    handler_command(fd_command, this_controller);
                    controller_save(this_controller, mdb_env);
                    break;
            }
        }
        if(fds[i].revents & POLLHUP)
        {
            /* A hangup has occurred on device number i. */
            LOG_DEBUG("fd %i got closed", fds[i].fd);
        }
    }
}

/**
 * @brief The main function
 *
 * @param argc UNUSED
 * @param argv UNUSED
 *
 * @return Statuscode to indicate success (0) or failure (!0)
 */
int
main(int argc, char** argv)
{
    (void)argc;
    (void)argv;

    signal(SIGINT, terminate);
    signal(SIGABRT, terminate);
    signal(SIGTERM, terminate);

    if(sizeof(time_t) < 8)
    {
        LOG_WARN("this system is not using 8-bit time");
    }

    /******************** SETUP DATABASE AND THIS CONTROLLER ********************/

    database_setup(&mdb_env);

    this_controller = controller_load(mdb_env);

    fd_discovery = helper_open_discovery_socket(this_controller->discovery_port);
    fd_command = helper_bind_tcp_server("0.0.0.0", this_controller->command_port, 128);

    this_controller->command_port = helper_get_port(fd_command);

    controller_save(this_controller, mdb_env);

    
    /******************** SETUP WIRINGPI ********************/

    wiringPiSetupSys();
    piFaceSetup(200);


    /******************** SETUP SOCKETS ********************/

    struct pollfd fds[2];
    int timeout_msecs = ACCEPT_TIMEOUT_MSECONDS;
    int ret;
    int fd_count = 0;

    /* Open STREAMS device. */
    fds[POLL_FGS_DISCOVERY].fd = fd_discovery;
    fds[POLL_FGS_DISCOVERY].events = POLLIN;
    LOG_DEBUG("setup fd_discovery as %i on index %i", fd_discovery, fd_count);
    fd_count++;
    fds[POLL_FGS_COMMAND].fd = fd_command;
    fds[POLL_FGS_COMMAND].events = POLLIN;
    LOG_DEBUG("setup fd_command as %i on index %i", fd_command, fd_count);
    fd_count++;

    /******************** START MAIN LOOP ********************/

    for(;;)
    {
        ret = poll(fds, fd_count, timeout_msecs);

        if(ret == 0)
        {
            LOG_DEBUG("===== IDLE LOOP START =====");
            for(uint_fast8_t i = 0; i < this_controller->relay_count; ++i)
            {
                relay_t *relay = this_controller->relays[i];
                if(relay_is_active(relay, time(NULL)))
                {
                    LOG_DEBUG("relay %d is active", i);
                    if(relay->number >= 2)
                    {
                        driver_gpio_set(relay, HIGH);
                    }
                    else
                    {
                        driver_piface_set(relay, HIGH);
                    }
                }
                else
                {
                    if(relay->number >= 2)
                    {
                        driver_gpio_set(relay, LOW);
                    }
                    else
                    {
                        driver_piface_set(relay, LOW);
                    }
                }
            }
        }
        if(ret > 0)
        {
            handle_poll(fds, fd_count);
        }
    }

    close(fd_discovery);

    mdb_env_close(mdb_env);

    return 0;
}