#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include <sys\types.h> 
#include <sys\stat.h>

#include "GameController.h"
#include "Kempston.h"
#include "Keyboard.h"
#include "Log.h"
#include "ConfigFileReader.h"
#include "Rewind.h"

Uint8 _gameControllerInitialized = 0;
SDL_GameController* _controller = NULL;
int _controllerJoystickIndex = -1;

Uint8 _previousAxisMotion = 0;

// these determine the random interval of frames to wait between autofire flips
// fastest human button tapping is 15-20 presses per second, according
// to online sources (equals 30ms-40ms per flip)
// ZX Spectrum frame duration is 20ms (PAL video system is 50Hz)
#define AUTOFIRE_FRAMES_MIN 2            // at least 40ms
#define AUTOFIRE_FRAMES_INTERVAL_WIDTH 4 // 40ms-100ms

// different controllers raise directional events differently:
// 
//                            |  left DPAD events   |   left thumbstick events
// ------------------------------------------------------------------------------
// steelseries wired USBC          axis motion                axis motion
// noname SNES wired USBC          axis motion                axis motion
// nintendo switch pro           button up/down               axis motion
//
// these are all translated to "button up/down" events before being delegated
// to the _controller_act_on_button_* functions

//
//  resolution path:
//
//     SDL axis motion >----+                                     +----> Keyboard.h "key up/down"
//         event            |                                     |
//                          +----> SDL button ----> mapping ----> +----> Unassigned (NOOP)
//                          |                          ^          |
//  SDL button up/down >----+                          |          +----> Kempston.h "button up/down"
//         event                                       |          |
//                                                     |          +----> utilities (rewind, etc.)
//                                config file ----> override
//

enum utilityButton {
    Rewind
};

// identifier of the target of a controller button mapping
union targetSDLButtonCode {
    SDL_Keycode SDLKeysym;
    Uint8 kempstonFlag;
    enum utilityButton utilityButtonInfo;
};

// holds information needed to use autofire for a button mapping
struct autoFireState {
    // when 0, autofire is not in use at all for this controller mapping
    Uint8 isInUse;
    // when it reaches 0 (counting down), target button flips between down/up
    Uint8 frameCounter;
    // state of the target button, which oscillates between down/up
    Uint8 isTargetButtonDown;
};

// controller buttons can be mapped to one of the following types
enum controllerToSdlMappingType {
    UnassignedButton, SdlKeyboardButton, KempstonButton, Utility
};

// sdl button code ----> keyboard/kempston/etc. button code
struct gameControllerButtonMapping {
    enum controllerToSdlMappingType type;
    Uint8 sdlControllerButtonCode;
    const char* name;
    struct autoFireState autoFireState;
    // state of the source button (on the controller), which - when held down -
    // makes the target button oscillate
    Uint8 isControllerButtonDown;
    union targetSDLButtonCode targetButtonCode;
};

#define NUM_BUTTON_MAPPINGS 10
static struct gameControllerButtonMapping _buttonMappings[NUM_BUTTON_MAPPINGS] = {
    {.type = Utility, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_LEFTSHOULDER, .name = "controller_left_shoulder",
        .autoFireState.isInUse = 0, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.utilityButtonInfo = Rewind},
    {.type = KempstonButton, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, .name = "controller_right_shoulder", 
        .autoFireState.isInUse = 1, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.kempstonFlag = KEMPSTON_FIRE},

    {.type = KempstonButton, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_A, .name = "controller_A", 
        .autoFireState.isInUse = 0, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.kempstonFlag = KEMPSTON_FIRE},
    {.type = KempstonButton, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_B, .name = "controller_B", 
        .autoFireState.isInUse = 0, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.kempstonFlag = KEMPSTON_FIRE},
    {.type = KempstonButton, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_X, .name = "controller_X", 
        .autoFireState.isInUse = 0, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.kempstonFlag = KEMPSTON_FIRE},
    {.type = KempstonButton, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_Y, .name = "controller_Y", 
        .autoFireState.isInUse = 0, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.kempstonFlag = KEMPSTON_FIRE},

    {.type = KempstonButton, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_DPAD_UP, .name = "controller_dpad_UP", 
        .autoFireState.isInUse = 0, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.kempstonFlag = KEMPSTON_UP},
    {.type = KempstonButton, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_DPAD_DOWN, .name = "controller_dpad_DOWN", 
        .autoFireState.isInUse = 0, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.kempstonFlag = KEMPSTON_DOWN},
    {.type = KempstonButton, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_DPAD_LEFT, .name = "controller_dpad_LEFT", 
        .autoFireState.isInUse = 0, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.kempstonFlag = KEMPSTON_LEFT},
    {.type = KempstonButton, .sdlControllerButtonCode = SDL_CONTROLLER_BUTTON_DPAD_RIGHT, .name = "controller_dpad_RIGHT", 
        .autoFireState.isInUse = 0, .autoFireState.frameCounter = 0, .autoFireState.isTargetButtonDown = 0, 
        .isControllerButtonDown = 0, .targetButtonCode.kempstonFlag = KEMPSTON_RIGHT},
};

struct gameControllerButtonMapping* _getMappingByName(const char* key) {
    for (int i = 0; i < NUM_BUTTON_MAPPINGS; i++) {
        if (!strcmp(_buttonMappings[i].name, key)) {
            return &_buttonMappings[i];
        }
    }

    return NULL;
}

struct gameControllerButtonMapping* _getMappingBySdlButtonCode(Uint8 sdlButton) {
    for (int i = 0; i < NUM_BUTTON_MAPPINGS; i++) {
        if (_buttonMappings[i].sdlControllerButtonCode == sdlButton) {
            return &_buttonMappings[i];
        }
    }

    return NULL;
}

void _game_controller_set_override(const char* overrideSourceString, const char* overrideTarget) {
    Uint8 kempstonButtonFlag;
    SDL_Keycode sdlKeyCode;

    struct gameControllerButtonMapping* mapping = _getMappingByName(overrideSourceString);
    if (mapping == NULL) {
        log_write_string_string_string_string("Warning: game controller - cannot match controller button %s from config mapping (%s, %s)",
            (char*)overrideSourceString, (char*)overrideSourceString, (char*)overrideTarget);
        return;
    }

    if (!strcmp(overrideTarget, "unassigned")) {
        // the override unassigns the controller button
        mapping->type = UnassignedButton;
        return;
    }

    mapping->isControllerButtonDown = 0;

    if (!strcmp(overrideTarget, "rewind")) {
        // utility: rewind
        mapping->type = Utility;
        mapping->targetButtonCode.utilityButtonInfo = Rewind;
        return;
    }

    // is this an autofire mapping?
    const char* autofirePrefix = "AUTO_";
    if (strstr(overrideTarget, autofirePrefix) == overrideTarget) {
        // yes, the target starts with the autofire prefix
        
        // go past the autofire prefix
        overrideTarget+=strlen(autofirePrefix);

        // initialize autofire state
        mapping->autoFireState.isInUse = 1; // enable autofire
        mapping->autoFireState.frameCounter = 0; // flip immediately next time user presses
        mapping->autoFireState.isTargetButtonDown = 0;
    }

    // see what we're mapping to
    if (kempston_try_resolve_button_flag_from_override(overrideTarget, &kempstonButtonFlag)) {
        // the override is targetting a kempston button
        mapping->type = KempstonButton;
        mapping->targetButtonCode.kempstonFlag = kempstonButtonFlag;
    }
    else if (keyboard_try_resolve_keysym_from_override(overrideTarget, &sdlKeyCode)) {
        // the override is targetting a keyboard key
        mapping->type = SdlKeyboardButton;
        mapping->targetButtonCode.SDLKeysym = sdlKeyCode;
    }
    else {
        log_write_string_string_string_string("Warning: game controller - cannot match target %s from config mapping (%s, %s)",
            (char*)overrideTarget, (char*)overrideSourceString, (char*)overrideTarget);
    }
}

void _game_controller_act_on_button_down__perform(struct gameControllerButtonMapping* mapping)
{
    switch (mapping->type)
    {
    case Utility:
        switch (mapping->targetButtonCode.utilityButtonInfo)
        {
        case Rewind:
            rewind_schedule_load();
        default:
            break;
        }
    case SdlKeyboardButton:
        keyboard_keydown_by_code(mapping->targetButtonCode.SDLKeysym);
        break;
    case KempstonButton:
        kempston_buttondown(mapping->targetButtonCode.kempstonFlag);
        break;
    case UnassignedButton:
    default:
        break;
    }
}

void _game_controller_act_on_button_down(Uint8 sdlButton)
{
    struct gameControllerButtonMapping* mapping = _getMappingBySdlButtonCode(sdlButton);
    if (!mapping) {
        return;
    }

    mapping->isControllerButtonDown = 1;

    _game_controller_act_on_button_down__perform(mapping);
}

void game_controller_on_button_down(SDL_Event event)
{
    if (!_gameControllerInitialized) {
        return;
    }

    if (event.cdevice.which != _controllerJoystickIndex) {
        // event came from another joystick
        return;
    }

    _game_controller_act_on_button_down(event.cbutton.button);
}

void _game_controller_act_on_button_up__perform(struct gameControllerButtonMapping* mapping)
{
    switch (mapping->type)
    {
    case SdlKeyboardButton:
        keyboard_keyup_by_code(mapping->targetButtonCode.SDLKeysym);
        break;
    case KempstonButton:
        kempston_buttonup(mapping->targetButtonCode.kempstonFlag);
        break;
    case UnassignedButton:
    default:
        break;
    }
}

void _game_controller_act_on_button_up(Uint8 sdlButton)
{
    struct gameControllerButtonMapping* mapping = _getMappingBySdlButtonCode(sdlButton);
    if (!mapping) {
        return;
    }

    mapping->isControllerButtonDown = 0;

    _game_controller_act_on_button_up__perform(mapping);
}

void game_controller_on_button_up(SDL_Event event)
{
    if (!_gameControllerInitialized) {
        return;
    }

    if (event.cdevice.which != _controllerJoystickIndex) {
        // event came from another joystick
        return;
    }

    _game_controller_act_on_button_up(event.cbutton.button);
}

Uint8 _hasAxisJustReleased(Uint8 currentAxieValue, Uint8 direction) {
    return (_previousAxisMotion & direction) && !(currentAxieValue & direction);
}

Uint8 _hasAxisJustMoved(Uint8 currentAxieValue, Uint8 direction) {
    return !(_previousAxisMotion & direction) && (currentAxieValue & direction);
}

void game_controller_on_controller_axis_motion(SDL_Event event)
{
    //
    // internally in this function we rely on SDL_HAT_ flags
    // because of the convenience of not having to define our own flags
    //

    if (!_gameControllerInitialized) {
        return;
    }

    if (event.cdevice.which != _controllerJoystickIndex) {
        // event came from another joystick
        return;
    }

    if (event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX) {
        // X axis
        Uint8 xAxisFlags = 0;

        Sint16 axisValue = event.caxis.value;
        if (axisValue < -32768 / 2) {
            // left of dead zone
            xAxisFlags |= SDL_HAT_LEFT;
        }
        else if (axisValue > 32767 / 2) {
            // right of dead zone
            xAxisFlags |= SDL_HAT_RIGHT;
        }
        else {
            // centered
        }

        Uint8 newPreviousAxisMotion = _previousAxisMotion;

        if (_hasAxisJustMoved(xAxisFlags, SDL_HAT_LEFT)) {
            _game_controller_act_on_button_down(SDL_CONTROLLER_BUTTON_DPAD_LEFT);
            newPreviousAxisMotion |= SDL_HAT_LEFT;
        }
        else if (_hasAxisJustReleased(xAxisFlags, SDL_HAT_LEFT)) {
            _game_controller_act_on_button_up(SDL_CONTROLLER_BUTTON_DPAD_LEFT);
            newPreviousAxisMotion &= ~SDL_HAT_LEFT;
        }

        if (_hasAxisJustMoved(xAxisFlags, SDL_HAT_RIGHT)) {
            _game_controller_act_on_button_down(SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
            newPreviousAxisMotion |= SDL_HAT_RIGHT;
        }
        else if (_hasAxisJustReleased(xAxisFlags, SDL_HAT_RIGHT)) {
            _game_controller_act_on_button_up(SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
            newPreviousAxisMotion &= ~SDL_HAT_RIGHT;
        }

        _previousAxisMotion = newPreviousAxisMotion;

    } 
    
    if (event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY) {
        // Y axis
        Uint8 yAxisFlags = 0;

        Sint16 axisValue = event.caxis.value;
        if (axisValue < -32768 / 2) {
            // up of dead zone
            yAxisFlags |= SDL_HAT_UP;
        }
        else if (axisValue > 32767 / 2) {
            // down of dead zone
            yAxisFlags |= SDL_HAT_DOWN;
        }
        else {
            // centered
        }

        Uint8 newPreviousAxisMotion = _previousAxisMotion;
        if (_hasAxisJustMoved(yAxisFlags, SDL_HAT_UP)) {
            _game_controller_act_on_button_down(SDL_CONTROLLER_BUTTON_DPAD_UP);
            newPreviousAxisMotion |= SDL_HAT_UP;
        }
        else if (_hasAxisJustReleased(yAxisFlags, SDL_HAT_UP)) {
            _game_controller_act_on_button_up(SDL_CONTROLLER_BUTTON_DPAD_UP);
            newPreviousAxisMotion &= ~SDL_HAT_UP;
        }

        if (_hasAxisJustMoved(yAxisFlags, SDL_HAT_DOWN)) {
            _game_controller_act_on_button_down(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
            newPreviousAxisMotion |= SDL_HAT_DOWN;
        }
        else if (_hasAxisJustReleased(yAxisFlags, SDL_HAT_DOWN)) {
            _game_controller_act_on_button_up(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
            newPreviousAxisMotion &= ~SDL_HAT_DOWN;
        }

        _previousAxisMotion = newPreviousAxisMotion;
    }
}

// callback
void _game_controller_process_config_property(char* name, char* value) {
    _game_controller_set_override(name, value);
}

void game_controller_configure_overrides_from_file(char* fileName)
{
    log_write_string_string("Game controller: loading from config file: %s", fileName);
    config_file_reader__read_config_properties(fileName, _game_controller_process_config_property);
    log_write("Game controller: finished parsing config file");
}

void game_controller_notify_start_of_frame() {
    if (!_gameControllerInitialized) {
        return;
    }

    // advance autofire states
    for (int i = 0; i < NUM_BUTTON_MAPPINGS; i++) {
        struct gameControllerButtonMapping* mapping = &_buttonMappings[i];
        if (!mapping->autoFireState.isInUse) {
            continue;
        }
        // this mapping is autofire-based

        if (!mapping->isControllerButtonDown) {
            continue;
        }
        // ... and the user is holding down the controller button

        if (mapping->autoFireState.frameCounter == 0) {
            // it's time to flip the target button
            mapping->autoFireState.isTargetButtonDown ^= 1;

            if (mapping->autoFireState.isTargetButtonDown) {
                _game_controller_act_on_button_down__perform(mapping);
            }
            else {
                _game_controller_act_on_button_up__perform(mapping);
            }

            // reset frame counter
            // a random number of frames is used in case certain games check inputs
            // every several frames - in which case a non-random number of frames 
            // might synchronize with the game's checking, resulting in autofire
            // not working
            mapping->autoFireState.frameCounter = AUTOFIRE_FRAMES_MIN + 
                rand() % (2 + AUTOFIRE_FRAMES_INTERVAL_WIDTH - AUTOFIRE_FRAMES_MIN);
        }
        else {
            // advance frames
            mapping->autoFireState.frameCounter--;
        }
    }
}

void game_controller_notify_end_of_frame() {
    if (!_gameControllerInitialized) {
        return;
    }
}

void game_controller_start()
{
    if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
        log_write("Warning: game controller - failed to initialize game controller module");
        return;
    }

    int numJoysticks = SDL_NumJoysticks();
    if (numJoysticks == 0) {
        log_write("Warning: game controller - no joysticks available");
        return;
    }

    int i;
    for (i = 0; i < numJoysticks; i++) {
        if (SDL_IsGameController(i)) {
            _controller = SDL_GameControllerOpen(i);
            if (!_controller) {
                log_write_string_int("Warning: game controller - found controller at joystick index %d but could not open", i);
            }
            else {
                log_write_string_int("Game controller: found at joystick index %d and opened successfully", i);
                _controllerJoystickIndex = i;
                break;
            }
        }
    }
    if (i >= numJoysticks) {
        log_write("Warning: game controller - no game controllers could be opened");
        return;
    }

    // a controller has been found
    SDL_GameControllerType controllerType = SDL_GameControllerGetType(_controller);
    log_write_string_int("Game controller: type is %d", controllerType);

    _gameControllerInitialized = 1;
}

void game_controller_destroy()
{
    if (!_gameControllerInitialized) {
        return;
    }
}
