#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 <constants.h>
#include <handlers.h>
#include <drivers.h>
#include <enums.h>
#include <runners.h>
#include <helpers.h>
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <confini.h>

config_t global_config;

static MDB_env *mdb_env;
static controller_t *this_controller;
static struct pollfd poll_fds[POLL_FDS_COUNT];

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

    for(int i = 0; i < POLL_FDS_COUNT; ++i)
    {
        close(poll_fds[i].fd);
    }

    mdb_env_close(mdb_env);

    controller_free(this_controller);

    free(global_config.database);
    free(global_config.relay_configs);

    exit(signum);
}

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

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

    /******************** LOAD CONFIG ********************/

    global_config.file = "controller.ini";
    global_config.log_level = LOG_LEVEL_INFO;

    helper_parse_cli(argc, argv, &global_config);

    FILE * const ini_file = fopen(global_config.file, "rb");
    if(ini_file == NULL)
    {
        LOG_FATAL("config file '%s' was not found\n", global_config.file);
        exit(1);
    }
    if(load_ini_file( ini_file, INI_DEFAULT_FORMAT, NULL, helper_load_config, &global_config))
    {
        LOG_FATAL("unable to parse ini file\n");
        exit(1);
    }
 
    fclose(ini_file);

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

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

    database_setup(&mdb_env, &global_config);

    this_controller = controller_load(mdb_env);

    int fd_discovery = helper_open_discovery_socket(this_controller->discovery_port);
    int 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 ********************/

    wiringPiSetup();
    piFaceSetup(PIFACE_GPIO_BASE);

    for(uint_fast8_t i = 0; i < this_controller->relay_count; ++i)
    {
        if(global_config.relay_configs[i].driver == RELAY_DRIVER_GPIO)
        {
            pinMode(global_config.relay_configs[i].pin, OUTPUT);
        }
    }


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

    int timeout_msecs = ACCEPT_TIMEOUT_MSECONDS;
    int ret;

    /* Open STREAMS device. */
    poll_fds[POLL_FDS_DISCOVERY].fd = fd_discovery;
    poll_fds[POLL_FDS_DISCOVERY].events = POLLIN;
    LOG_DEBUG("setup fd_discovery as %i on index %i\n", fd_discovery, POLL_FDS_DISCOVERY);
    poll_fds[POLL_FDS_COMMAND].fd = fd_command;
    poll_fds[POLL_FDS_COMMAND].events = POLLIN;
    LOG_DEBUG("setup fd_command as %i on index %i\n", fd_command, POLL_FDS_COMMAND);


    /******************** CHECK FOR TESTING RUN ********************/

    if(global_config.run_type == RUN_TYPE_TEST)
    {
        runner_test(this_controller);
        terminate(0);
    }
    

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

    for(;;)
    {
        ret = poll(poll_fds, POLL_FDS_COUNT, timeout_msecs);

        if(ret == 0)
        {
            handler_loop(this_controller);
        }
        if(ret > 0)
        {
            handler_poll(poll_fds, this_controller, mdb_env);
        }
    }

    close(fd_discovery);

    mdb_env_close(mdb_env);

    return 0;
}