#ifndef _HPP_KUNGFLOW_CHARACTER_HUMANCONTROLLER
#define _HPP_KUNGFLOW_CHARACTER_HUMANCONTROLLER

#include "gaia/gaia.hpp"
using namespace gaia;

#include "character_controller.hpp"

#include <boost/signals2.hpp>

#include <deque>

class KungFlowGame;
class PlayState;
class Stage;
class CharacterModelView;
class HumanGamer;
class Character;
//class Sequence;

/*
struct SequenceCache {
  Sequence *sequence;
  unsigned long creationTime_ms;
  bool inUse;
};
*/

struct ButtonStatus {
  ButtonStatus() {
    this->buttonID = 10000; // sane 'never used' default
    this->pressed = false;
  }
  ButtonStatus(unsigned int buttonID, bool pressed) {
    this->buttonID = buttonID;
    this->pressed = pressed;
  }
  unsigned int buttonID; // like 'ingame function' id, not actual HID button id
  bool pressed; // true when pressed, false when released
};

/*
enum e_QuantizationType {
  e_QuantizationType_Digital,
  e_QuantizationType_Analog
};
*/

struct Input {
  Input() {
    //processed = false;
    //quantizationType = e_QuantizationType_Digital;
    _id = 0;
  }
  //unsigned int buttonID; // like 'ingame function' id, not actual HID button id
  //bool pressed; // true when pressed, false when released
  ButtonStatus buttonAction;
  unsigned long timestamp_ms;
  //bool processed;
  //e_QuantizationType quantizationType;
  unsigned long _id;
};

class InputHistory {

  // records input, so we can extract combo's and such out of it

  public:

    InputHistory() : maxEntries(16) {
      this->processedUpToID = 0;
      this->currentHistoryID = 0;
    }

    virtual ~InputHistory() {
    }

    // for debug purposes
    void PrintHistory() {
      std::deque<Input>::const_iterator historyIter = history.begin();
      while (historyIter != history.end()) {
        printf("[%lu] %i: %i\n", (*historyIter)._id, (*historyIter).buttonAction.buttonID, (*historyIter).buttonAction.pressed);
        historyIter++;
      }
    }

    void PrintStatus() {
      for (unsigned int i = 0; i < buttonStatus.size(); i++) {
        printf("%i == %i\n", buttonStatus.at(i).buttonID, buttonStatus.at(i).pressed);
      }
    }

    void TryInsertEntry(const Input &input) {
      // only insert if there's a need to update
      // *previously: if (input.buttonID is in history && input.pressed != same as prev history) update
      // *new: if (input.buttonID is in buttonStatus && input.pressed != same as in status) insert into history
      /*
      bool prevPressed = false;
      if (FindLastStatus(input.buttonAction.buttonID, prevPressed) == true) {
        if (prevPressed != input.buttonAction.pressed) {
          //printf("%i (%i -> %i)\n", input.buttonAction.buttonID, prevPressed, input.buttonAction.pressed);
          //printf("%i\n", input.buttonAction.pressed);
          history.push_back(input);
        }
      } else {
        //printf("%i (x -> %i)\n", input.buttonAction.buttonID, input.buttonAction.pressed);
        // not found in history, can safely add
        history.push_back(input);
      }
      */

      bool isPressed = false;
      bool statusFound = FindCurrentStatus(input.buttonAction.buttonID, isPressed);
      if (!statusFound || isPressed != input.buttonAction.pressed) {
        UpdateStatus(input.buttonAction.buttonID, input.buttonAction.pressed);
        history.push_back(input);
        (*--history.end())._id = ++this->currentHistoryID;
        if (history.size() > maxEntries) {
          if (processedUpToID <= history.begin()->_id) SetProcessedUpTo( (*++history.begin())._id );
          history.pop_front();
        }
      }

    }

    bool FindCurrentStatus(unsigned buttonID, bool &result) {
      for (unsigned int i = 0; i < buttonStatus.size(); i++) {
        if (buttonStatus.at(i).buttonID == buttonID) {
          result = buttonStatus.at(i).pressed;
          return true;
        }
      }
      return false;
    }

    void UpdateStatus(unsigned buttonID, bool isPressed) {
      for (unsigned int i = 0; i < buttonStatus.size(); i++) {
        if (buttonStatus.at(i).buttonID == buttonID) {
          buttonStatus.at(i).pressed = isPressed;
          return;
        }
      }
      // not yet in vector, apparently
      ButtonStatus status;
      status.buttonID = buttonID;
      status.pressed = isPressed;
      buttonStatus.push_back(status);
    }

/*
    bool FindLastStatus(unsigned int buttonID, bool &result) const { // returns false if not found at all
      std::deque<Input>::const_reverse_iterator historyIter = history.rbegin();
      while (historyIter != history.rend()) {
        //printf("looking for button: %i, button in history: %i\n", buttonID, (*historyIter).buttonAction.buttonID);
        if ((*historyIter).buttonAction.buttonID == buttonID) {
          result = (*historyIter).buttonAction.pressed;
          return true;
        }
        historyIter++;
      }
      return false;
    }
*/


    bool FindCombo(const std::vector<ButtonStatus> &buttonActions, unsigned long time_ms, unsigned int allowedDuration_ms, bool ignoreFirstButtonTime, unsigned int &elapsedTime_ms, unsigned long &lastEntryID, bool includeProcessed = true) const {

      std::vector<ButtonStatus>::const_reverse_iterator buttonIter = buttonActions.rbegin();
      std::deque<Input>::const_reverse_iterator historyIter = history.rbegin();
      bool found = false;
      unsigned long lastButtonTimestamp_ms = 0;

      while (historyIter != history.rend()) {

        if ((*buttonIter).buttonID == (*historyIter).buttonAction.buttonID &&
            (*buttonIter).pressed == (*historyIter).buttonAction.pressed &&
            //( (*historyIter).processed == false || includeProcessed ) &&
            ( processedUpToID < (*historyIter)._id || includeProcessed ) &&
            //(*historyIter).timestamp_ms + allowedDuration_ms > time_ms) {
            //( (*historyIter).timestamp_ms + allowedDuration_ms > time_ms || (buttonActions.back().pressed == false && buttonIter == buttonActions.rend()) )) { // if this combo ends in a key release then we (probably) don't care how long ago the first button was pressed
            ( (*historyIter).timestamp_ms + allowedDuration_ms > time_ms || (ignoreFirstButtonTime && buttonIter == --buttonActions.rend()) )) { // if this combo ends in a key release then we may not care how long ago the first button was pressed

          //printf("found one!\n");
          if (buttonIter == buttonActions.rbegin()) {
            lastButtonTimestamp_ms = (*historyIter).timestamp_ms;
            lastEntryID = (*historyIter)._id;
          }

          buttonIter++; // found one! move on

          if (buttonIter == buttonActions.rend()) {
            elapsedTime_ms = lastButtonTimestamp_ms - (*historyIter).timestamp_ms;
            //printf("last one: %lu\n", lastEntryID);
            //if (!allEntriesAlreadyProcessed) found = true;
            found = true;
            break;
          }

        //} else if ((*buttonIter).pressed == (*historyIter).buttonAction.pressed) break; // there was some key in there that didn't belong to the combo (ignore correct key with wrong press state, though)
        } else if ((*buttonIter).buttonID != (*historyIter).buttonAction.buttonID) break; // there was some key in there that didn't belong to the combo (ignore correct key with wrong press state, though)

        historyIter++;
      }
      // ^ function: if historyIter reached end(), but buttonIter didn't, combo wasn't found.

      return found;
    }

    /*
    // this function assumes the given combo is actually in the history (just found with FindCombo, for example); else undocumented behaviour may occur
    void MarkComboAsProcessed(const std::vector<ButtonAction> &buttonActions, unsigned long time_ms, unsigned int allowedDuration_ms, bool ignoreFirstButtonTime) {

      std::vector<ButtonAction>::const_reverse_iterator buttonIter = buttonActions.rbegin();
      std::deque<Input>::reverse_iterator historyIter = history.rbegin();
      bool complete = false;

      while (historyIter != history.rend()) {

        if ((*buttonIter).buttonID == (*historyIter).buttonAction.buttonID &&
            (*buttonIter).pressed == (*historyIter).buttonAction.pressed &&
            (*historyIter).processed == false &&
            //(*historyIter).timestamp_ms + allowedDuration_ms > time_ms) {
            //( (*historyIter).timestamp_ms + allowedDuration_ms > time_ms || (buttonActions.back().pressed == false && buttonIter == buttonActions.rend()) )) { // if this combo ends in a key release then we (probably) don't care how long ago the first button was pressed
            ( (*historyIter).timestamp_ms + allowedDuration_ms > time_ms || (ignoreFirstButtonTime && buttonIter == --buttonActions.rend()) )) { // if this combo ends in a key release then we may not care how long ago the first button was pressed

          //printf("found one!\n");
          (*historyIter).processed = true;
          buttonIter++;
          if (buttonIter == buttonActions.rend()) {
            //printf("finished!\n");
            complete = true;
            break;
          }

        }

        historyIter++;
      }

      assert(complete == true); // if not, caller made a booboo (maybe working on an invalid history?)
    }
*/

/*
    bool FindCombo(const std::vector<ButtonStatus> &buttonActions, unsigned long time_ms, unsigned int allowedDuration_ms, bool ignoreFirstButtonTime, unsigned int &elapsedTime_ms, unsigned long &lastEntryID, bool includeProcessed = true) const {

      std::vector<ButtonStatus>::const_iterator buttonIter = buttonActions.begin();
      std::deque<Input>::const_iterator historyIter = history.begin();
      bool found = false;
      unsigned long firstButtonTimestamp_ms = 0;

      while (historyIter != history.end()) {

        if ((*buttonIter).buttonID == (*historyIter).buttonAction.buttonID &&
            (*buttonIter).pressed == (*historyIter).buttonAction.pressed &&
            //( (*historyIter).processed == false || includeProcessed ) &&
            ( processedUpToID < (*historyIter)._id || includeProcessed ) &&
            //(*historyIter).timestamp_ms + allowedDuration_ms > time_ms) {
            //( (*historyIter).timestamp_ms + allowedDuration_ms > time_ms || (buttonActions.back().pressed == false && buttonIter == buttonActions.begin()) )) { // if this combo ends in a key release then we (probably) don't want to take into account how long ago the first button was pressed
            ( (*historyIter).timestamp_ms + allowedDuration_ms > time_ms || (ignoreFirstButtonTime && buttonIter == buttonActions.begin()) )) { // if this combo ends in a key release then we may not care how long ago the first button was pressed

          //printf("found one! (%i)\n", (*buttonIter).pressed);
          if (buttonIter == buttonActions.begin()) firstButtonTimestamp_ms = (*historyIter).timestamp_ms;

          buttonIter++; // found one! move on

          if (buttonIter == buttonActions.end()) {
            elapsedTime_ms = (*historyIter).timestamp_ms - firstButtonTimestamp_ms;
            lastEntryID = (*historyIter)._id;
            printf("last one: %lu\n", lastEntryID);
            //printf("finished!\n");
            //if (!allEntriesAlreadyProcessed) found = true;
            found = true;
            break;
          }

        } else if (buttonIter != buttonActions.begin()) break; // there was some key in there that didn't belong to the combo

        historyIter++;
      }
      // ^ function: if historyIter reached end(), but buttonIter didn't, combo wasn't found.

      return found;
    }
*/

    void SetProcessedUpTo(unsigned long historyID) {
      //printf("last one [set]: %lu\n", historyID);
      processedUpToID = historyID;
      /*
      std::deque<Input>::const_iterator historyIter = history.begin();
      while (historyIter != history.end()) {
        *historyIter
        while ((*historyIter).id != historyID)
        historyIter++;
      }
      */
    }

    /*
    // this function assumes the given combo is actually in the history (just found with FindCombo, for example); else undocumented behaviour may occur
    void MarkComboAsProcessed(const std::vector<ButtonStatus> &buttonActions, unsigned long time_ms, unsigned int allowedDuration_ms, bool ignoreFirstButtonTime) {

      std::vector<ButtonStatus>::const_iterator buttonIter = buttonActions.begin();
      std::deque<Input>::iterator historyIter = history.begin();
      bool complete = false;

      while (historyIter != history.end()) {

        if ((*buttonIter).buttonID == (*historyIter).buttonAction.buttonID &&
            (*buttonIter).pressed == (*historyIter).buttonAction.pressed &&
            (*historyIter).processed == false &&
            //(*historyIter).timestamp_ms + allowedDuration_ms > time_ms) {
            //( (*historyIter).timestamp_ms + allowedDuration_ms > time_ms || (buttonActions.back().pressed == false && buttonIter == buttonActions.begin()) )) { // if this combo ends in a key release then we (probably) don't want to take into account how long ago the first button was pressed
            ( (*historyIter).timestamp_ms + allowedDuration_ms > time_ms || (ignoreFirstButtonTime && buttonIter == buttonActions.begin()) )) { // if this combo ends in a key release then we may not care how long ago the first button was pressed

          //printf("x found one!\n");
          (*historyIter).processed = true;
          buttonIter++;
          if (buttonIter == buttonActions.end()) {
            //printf("x finished!\n");
            complete = true;
            break;
          }

        } else if (buttonIter != buttonActions.begin()) break; // there was some key in there that didn't belong to the combo

        historyIter++;
      }

      assert(complete == true); // if not, caller made a booboo (maybe working on an invalid history?)
    }
    */

  private:

    std::vector<ButtonStatus> buttonStatus;
    std::deque<Input> history;
    unsigned long currentHistoryID;
    unsigned long processedUpToID;
    unsigned int maxEntries;

};

class CharacterHumanController : public CharacterController {

  public:

    CharacterHumanController(const KungFlowGame *game, const PlayState *playState, Character *character, const HumanGamer *humanGamer);
    virtual ~CharacterHumanController();

    virtual void OnStepPhysicsTime(CharacterModelView *characterModelView);

  private:

    const Character *character;
    const HumanGamer *humanGamer;
    InputHistory inputHistory;

    float throwBuffer;

    void OnThrow(const Vector2 &desiredMovement, const Vector2 &toTargetTranslation);
    boost::signals2::connection throwConnection;
    //SequenceCache createdSequence;

};

#endif
