// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation; either version 2 of the License, or
//   (at your option) any later version.
//  
//   This program is distributed in the hope that it will be useful, but
//   WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//   General Public License for more details.
// 
//   You should have received a copy of the GNU General Public License
//   along with this program; if not, write to the Free Software
//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include <iostream>
#include <vector>
#include <cassert>
#include <sstream>
using namespace std;

#include <SDL.h>

#include "input.h"

#include "../parser/parser.h"
using namespace LilyParser;

// The input system just uses some static variables here to keep track
// of everything. Possibly a bit hacky and ugly, but whatever.

/// Global bind lists.
static vector<InputBindDef> inputBinds;
static vector<InputBindDef> secondaryBinds;

/// All open joysticks.
static vector<SDL_Joystick*> joysticks;

/// Cleared and set every time someone inputPumpState()s.
static vector<int> inputStates;

static InputBindDef *safeGetBind(int keybindNum, bool secondary) {

    assert(keybindNum < 256); // Arbitrary limit. Yay!

    vector<InputBindDef> &bindList = secondary ? secondaryBinds : inputBinds;

    InputBindDef blankDef;
    inputClearBindDef(&blankDef);

    while((int)bindList.size() <= keybindNum) {
        
        bindList.push_back(blankDef);
    }

    return &(bindList[keybindNum]);

}


void inputClearBindDef(InputBindDef *def) {

    def->sdlKey = -1;
    def->joyNum = -1;
    def->joyAxis = -1;
    def->joyButton = -1;
    def->joyAxisDirection = -1;
}


void inputSetKeybind(int keybindNum, bool secondary,  InputBindDef *def) {

    InputBindDef *setDef = safeGetBind(keybindNum, secondary);

    *setDef = *def;
}


void inputGetKeybind(int keybindNum, bool secondary, InputBindDef *def) {

    InputBindDef *setDef = safeGetBind(keybindNum, secondary);

    *def = *setDef;
}


/// Fairly straightforward mapping of SDL keysyms to string
/// descriptions.
static string getStringForSDLKey(int key) {

    if((key >= 'a' && key <= 'z') ||
       (key >= 'A' && key <= 'Z') ||
       (key >= '0' && key <= '9')) {

        ostringstream outStr;
        outStr << (char)key;

        return outStr.str();
    }

    switch(key) {

    case SDLK_LEFT:         return "Left";
    case SDLK_RIGHT:        return "Right";
    case SDLK_UP:           return "Up";
    case SDLK_DOWN:         return "Down";

    case SDLK_BACKSPACE:    return "Backspace";
    case SDLK_TAB:          return "Tab";
    case SDLK_CLEAR:        return "Clear";
    case SDLK_RETURN:       return "Return";
    case SDLK_PAUSE:        return "Pause";
    case SDLK_ESCAPE:       return "Escape";
    case SDLK_SPACE:        return "Space";
    case SDLK_EXCLAIM:      return "!";
    case SDLK_QUOTEDBL:     return "\"";
    case SDLK_HASH:         return "#";
    case SDLK_DOLLAR:       return "$";
    case SDLK_AMPERSAND:    return "&";
    case SDLK_QUOTE:        return "'";
    case SDLK_LEFTPAREN:    return "(";
    case SDLK_RIGHTPAREN:   return ")";
    case SDLK_ASTERISK:     return "*";
    case SDLK_PLUS:         return "+";
    case SDLK_COMMA:        return ",";
    case SDLK_MINUS:        return "-";
    case SDLK_PERIOD:       return ".";
    case SDLK_SLASH:        return "/";

    case SDLK_COLON:        return ":";
    case SDLK_SEMICOLON:    return ";";
    case SDLK_LESS:         return "<";
    case SDLK_EQUALS:       return "=";
    case SDLK_GREATER:      return ">";
    case SDLK_QUESTION:     return "?";
    case SDLK_AT:           return "@";

    case SDLK_LEFTBRACKET:  return "[";
    case SDLK_BACKSLASH:    return "\\";
    case SDLK_RIGHTBRACKET: return "]";
    case SDLK_CARET:        return "^";
    case SDLK_UNDERSCORE:   return "_";
    case SDLK_BACKQUOTE:    return "`";

    case SDLK_DELETE:       return "Delete";

    case SDLK_KP0:          return "KP 0";
    case SDLK_KP1:          return "KP 1";
    case SDLK_KP2:          return "KP 2";
    case SDLK_KP3:          return "KP 3";
    case SDLK_KP4:          return "KP 4";
    case SDLK_KP5:          return "KP 5";
    case SDLK_KP6:          return "KP 6";
    case SDLK_KP7:          return "KP 7";
    case SDLK_KP8:          return "KP 8";
    case SDLK_KP9:          return "KP 9";
    case SDLK_KP_PERIOD:    return "KP .";
    case SDLK_KP_DIVIDE:    return "KP /";
    case SDLK_KP_MULTIPLY:  return "KP *";
    case SDLK_KP_MINUS:     return "KP -";
    case SDLK_KP_PLUS:      return "KP +";
    case SDLK_KP_ENTER:     return "KP Enter";
    case SDLK_KP_EQUALS:    return "KP =";

    case SDLK_INSERT:       return "Insert";
    case SDLK_HOME:         return "Home";
    case SDLK_END:          return "End";
    case SDLK_PAGEUP:       return "PageUp";
    case SDLK_PAGEDOWN:     return "PageDown";

    case SDLK_F1:           return "F1";
    case SDLK_F2:           return "F2";
    case SDLK_F3:           return "F3";
    case SDLK_F4:           return "F4";
    case SDLK_F5:           return "F5";
    case SDLK_F6:           return "F6";
    case SDLK_F7:           return "F7";
    case SDLK_F8:           return "F8";
    case SDLK_F9:           return "F9";
    case SDLK_F10:          return "F10";
    case SDLK_F11:          return "F11";
    case SDLK_F12:          return "F12";
    case SDLK_F13:          return "F13";
    case SDLK_F14:          return "F14";
    case SDLK_F15:          return "F15";

    case SDLK_RSHIFT:       return "Right Shift";
    case SDLK_LSHIFT:       return "Left Shift";
    case SDLK_RCTRL:        return "Right Control";
    case SDLK_LCTRL:        return "Left Control";
    case SDLK_RALT:         return "Right Alt";
    case SDLK_LALT:         return "Left Alt";
    case SDLK_RMETA:        return "Right Meta";
    case SDLK_LMETA:        return "Left Meta";
    case SDLK_LSUPER:       return "Left Super";
    case SDLK_RSUPER:       return "Right Super";

    default:
        return "???";

    }

}


std::string inputGetNameForBind(InputBindDef *def) {

    if(def->sdlKey != -1) {

        // It's a keyboard bind.
        return string("Keyboard: ") + getStringForSDLKey(def->sdlKey);
    }

    if(def->joyNum != -1) {

        // Joystick bind.

        ostringstream joyOut;

        joyOut << "Joystick " << def->joyNum << ": ";

        if(def->joyAxis != -1) {

            joyOut << "Axis " << def->joyAxis << " ";

            if(def->joyAxisDirection == 0) {

                joyOut << "-";

            } else {

                joyOut << "+";

            }

        } else {

            joyOut << "Button " << def->joyButton;

        }

        return joyOut.str();

    }

    // Looks like it's not actually bound.
    return "---";
}

void inputClearBinds(void) {
    inputBinds.clear();
    secondaryBinds.clear();
}

void inputInit(bool disableJoysticks) {

    if(!disableJoysticks) {

        SDL_InitSubSystem(SDL_INIT_JOYSTICK);

        for(int i = 0; i < SDL_NumJoysticks(); i++) {
            joysticks.push_back(SDL_JoystickOpen(i));
        }
    }
}

void inputShutdown(void) {

    for(unsigned int i = 0; i < joysticks.size(); i++) {
        SDL_JoystickClose(joysticks[i]);
    }

    joysticks.clear();

    SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}

bool inputGetKeybindState(int keybindNum) {

    if(keybindNum >= (int)inputStates.size()) {
        return false;
    }

    return inputStates[keybindNum];
}

int inputGetAxisState(int axisNum) {

    if(axisNum >= (int)inputStates.size()) {
        return false;
    }

    return inputStates[axisNum];
}

void inputPumpState(void) {

    Uint8 *keys = SDL_GetKeyState(NULL);
    inputStates.clear();

    // Set up a clear input state for everything.
    for(unsigned int i = 0; i < inputBinds.size() || i < secondaryBinds.size(); i++) {
        inputStates.push_back(0);
    }

    for(int k = 0; k < 2; k++) {

        vector<InputBindDef> &binds = (k == 0) ? inputBinds : secondaryBinds;

        for(unsigned int i = 0; i < binds.size(); i++) {

            int state;

            if(binds[i].sdlKey != -1) {
            
                // Bind uses keyboard. Check the key state.

                if(keys[binds[i].sdlKey]) {
                    state = INPUT_AXIS_MAX;
                } else {
                    state = 0;
                }

            } else if (binds[i].joyNum != -1 && binds[i].joyNum < int(joysticks.size())) {

                // Bind uses joystick. Check a button or axis.

                if(binds[i].joyAxis != -1) {

                    int realAxisValue = SDL_JoystickGetAxis(joysticks[binds[i].joyNum], binds[i].joyAxis);

                    if(!binds[i].joyAxisDirection) {

                        // Flip axis.
                        realAxisValue *= -1;
                    }

                    if(realAxisValue < 0) realAxisValue = 0;

                    state = realAxisValue;

                } else {
                
                    Uint8 buttonValue = SDL_JoystickGetButton(joysticks[binds[i].joyNum], binds[i].joyButton);

                    if(buttonValue) {
                        state = INPUT_AXIS_MAX;
                    } else {
                        state = 0;
                    }

                }

            } else {

                // No joystick or keyboard?
                state = 0;

            }

            // Use the highest value from the primary and secondary
            // inputs.
            if(state > inputStates[i]) {
                inputStates[i] = state;
            }

        }
    }
}

int inputGetNumJoysticks(void) {
    return joysticks.size();
}

void *inputGetRawJoystick(int joyNum) {

    if(joyNum >= (int)joysticks.size() || joyNum < 0) return NULL;

    return joysticks[joyNum];
}


void inputSetDefBasedOnWhateverJoysticksAreDoing(InputBindDef *def) {

    for(unsigned int i = 0; i < joysticks.size(); i++) {
        
        for(int axis = 0; axis < SDL_JoystickNumAxes(joysticks[i]); axis++) {

            int axisValue = SDL_JoystickGetAxis(joysticks[i], axis);

            // FIXME: Arbitrary deadzone?
            if(abs(axisValue) > 8192) {
                def->joyNum = i;
                def->joyAxis = axis;
                def->joyAxisDirection = (axisValue < 1) ? 0 : 1;
                return;
            }

        }

        for(int button = 0; button < SDL_JoystickNumButtons(joysticks[i]); button++) {

            if(SDL_JoystickGetButton(joysticks[i], button)) {
                def->joyNum = i;
                def->joyButton = button;
                return;
            }
            
        }

    }

}

static ParserNode *bindDefToParserNode(InputBindDef *def, int num) {
    
    ParserNode *node = new ParserNode("bind");
    
    node->setIntValue("id", num);
    if(def->sdlKey!= -1) node->setIntValue("keycode", def->sdlKey);
    if(def->joyNum != -1) node->setIntValue("joyNum", def->joyNum);
    if(def->joyAxis != -1) node->setIntValue("joyAxis", def->joyAxis);
    if(def->joyAxisDirection != -1) node->setIntValue("joyAxisDirection", def->joyAxisDirection);
    if(def->joyButton != -1) node->setIntValue("joyButton", def->joyButton);

    return node;
}

static void setBindDefFromNode(ParserNode *node, bool secondary) {
    
    int id = 0;
    node->getIntValue("id", &id);

    InputBindDef *def = safeGetBind(id, secondary);

    def->sdlKey = -1;
    def->joyNum = -1;
    def->joyAxis = -1;
    def->joyAxisDirection = -1;
    def->joyButton = -1;

    node->getIntValue("keycode", &(def->sdlKey));
    node->getIntValue("joyNum",  &(def->joyNum));
    node->getIntValue("joyAxis", &(def->joyAxis));
    node->getIntValue("joyAxisDirection", &(def->joyAxisDirection));
    node->getIntValue("joyButton", &(def->joyButton));
}

LilyParser::ParserNode *inputSaveBindsToParserNode(void) {

    ParserNode *root = new ParserNode("input");

    ParserNode *primary = new ParserNode("primary");
    ParserNode *secondary = new ParserNode("secondary");
    
    root->addChildToEnd(primary);
    root->addChildToEnd(secondary);

    for(unsigned int i = 0; i < inputBinds.size(); i++) {
        primary->addChildToEnd(bindDefToParserNode(&inputBinds[i], i));
    }

    for(unsigned int i = 0; i < secondaryBinds.size(); i++) {
        secondary->addChildToEnd(bindDefToParserNode(&secondaryBinds[i], i));
    }

    return root;
}

void inputLoadBindsFromParserNode(ParserNode *node) {
    
    for(int i = 0; i < 2; i++) {

        int childIndex = node->getChildIndexByName(i ? "secondary" : "primary");
        ParserNode *defSetNode = node->getChild(childIndex);

        for(unsigned int j = 0; j < defSetNode->getNumChildren(); j++) {

            ParserNode *bindDef = defSetNode->getChild(j);
            setBindDefFromNode(bindDef, i);
        }
    }
}


