#include "main.hpp"

#include "playstate.hpp"

#include <fmod_errors.h>
//#include "fmod_common.h"

#include "kungflowutils.hpp"

bool _optimize() {
  return true;
}

bool _verbose() {
  return false;
}

KungFlowGame::KungFlowGame(int desiredGraphicsTimeStep_ms, int desiredPhysicsTimeStep_ms) : Game(desiredGraphicsTimeStep_ms, desiredPhysicsTimeStep_ms) {

  SetMaximizedViewport(1.777777f);


  // audio

  FMOD_CHECK(FMOD_System_Create(&audioSystem)); // create the main system object
  unsigned int version;
  FMOD_CHECK(FMOD_System_GetVersion(GetAudioSystem(), &version));
  if (version < FMOD_VERSION) {
    printf("FMOD lib version %08x doesn't match header version %08x", version, FMOD_VERSION);
    exit(-1);
  }
  //FMOD_CHECK(FMOD_System_SetDSPBufferSize(GetAudioSystem(), 512, 2));
  FMOD_RESULT result = FMOD_System_Init(GetAudioSystem(), 32, FMOD_INIT_NORMAL, 0); // initialize FMOD
  if (result != FMOD_OK) {
    printf("FMOD error! (%d) %s\n", result, FMOD_ErrorString(result));
#ifdef __linux__
    if (result == FMOD_ERR_PLUGIN_MISSING) {
      printf("FMOD says: This can happen if your machine is missing the ALSA library 'libasound.so.2' for the desired architecture, almost any version of it will be sufficient.");
    }
#endif
    exit(-1);
  }

  FMOD_CHECK(FMOD_System_CreateChannelGroup(GetAudioSystem(), "master channel", &this->audioMasterChannelGroup));
  //FMOD_CHECK(FMOD_System_GetMasterChannelGroup(GetAudioSystem(), &this->audioMasterChannelGroup));

  FMOD_CHECK(FMOD_System_CreateChannelGroup(GetAudioSystem(), "sfx channel", &this->audioSFXChannelGroup));
  FMOD_CHECK(FMOD_ChannelGroup_AddGroup(GetAudioMasterChannelGroup(), GetAudioSFXChannelGroup(), true, nullptr));;
  FMOD_CHECK(FMOD_ChannelGroup_SetVolume(GetAudioSFXChannelGroup(), 1.0f));

  FMOD_CHECK(FMOD_System_CreateChannelGroup(GetAudioSystem(), "music channel", &this->audioMusicChannelGroup));
  FMOD_CHECK(FMOD_ChannelGroup_AddGroup(GetAudioMasterChannelGroup(), GetAudioMusicChannelGroup(), true, nullptr));;
  FMOD_CHECK(FMOD_ChannelGroup_SetVolume(GetAudioMusicChannelGroup(), 1.0f));

  FMOD_CHECK(FMOD_ChannelGroup_SetVolume(GetAudioMasterChannelGroup(), 1.0f));


  // font

  this->CreateFont(e_FontID_Debug, "res/fonts/unifont.ttf", 16);


  // the game state

  playState = new PlayState(this);

  ActivateState(playState);
}

KungFlowGame::~KungFlowGame() {

  DeactivateActiveState();

  delete playState;

  FMOD_CHECK(FMOD_ChannelGroup_Release(GetAudioMusicChannelGroup()));
  FMOD_CHECK(FMOD_ChannelGroup_Release(GetAudioSFXChannelGroup()));
  FMOD_CHECK(FMOD_ChannelGroup_Release(GetAudioMasterChannelGroup()));
  FMOD_CHECK(FMOD_System_Close(GetAudioSystem()));
  FMOD_CHECK(FMOD_System_Release(GetAudioSystem()));

  this->DeleteFont(e_FontID_Debug);
}

void KungFlowGame::OnKeyDown(SDL_Keysym keysym) {
  switch (keysym.sym) {

    case SDLK_ESCAPE:
      ActivateState(playState);
      break;


    case SDLK_LEFT:
      keyInput.left = true;
      break;

    case SDLK_RIGHT:
      keyInput.right = true;
      break;

    case SDLK_UP:
      keyInput.up = true;
      break;

    case SDLK_DOWN:
      keyInput.down = true;
      break;


    case SDLK_q:
      keyInput.actionkeys[0] = true;
      break;

    case SDLK_w:
      keyInput.actionkeys[1] = true;
      break;

    case SDLK_e:
      keyInput.actionkeys[2] = true;
      break;

    case SDLK_a:
      keyInput.actionkeys[3] = true;
      break;

    case SDLK_s:
      keyInput.actionkeys[4] = true;
      break;

    case SDLK_d:
      keyInput.actionkeys[5] = true;
      break;

    case SDLK_z:
      keyInput.actionkeys[6] = true;
      break;

    case SDLK_x:
      keyInput.actionkeys[7] = true;
      break;

    case SDLK_c:
      keyInput.actionkeys[8] = true;
      break;


    default:
      break;

  }
}

void KungFlowGame::OnKeyUp(SDL_Keysym keysym) {
  switch (keysym.sym) {

    case SDLK_LEFT:
      keyInput.left = false;
      break;

    case SDLK_RIGHT:
      keyInput.right = false;
      break;

    case SDLK_UP:
      keyInput.up = false;
      break;

    case SDLK_DOWN:
      keyInput.down = false;
      break;


    case SDLK_q:
      keyInput.actionkeys[0] = false;
      break;

    case SDLK_w:
      keyInput.actionkeys[1] = false;
      break;

    case SDLK_e:
      keyInput.actionkeys[2] = false;
      break;

    case SDLK_a:
      keyInput.actionkeys[3] = false;
      break;

    case SDLK_s:
      keyInput.actionkeys[4] = false;
      break;

    case SDLK_d:
      keyInput.actionkeys[5] = false;
      break;

    case SDLK_z:
      keyInput.actionkeys[6] = false;
      break;

    case SDLK_x:
      keyInput.actionkeys[7] = false;
      break;

    case SDLK_c:
      keyInput.actionkeys[8] = false;
      break;


    default:
      break;

  }
}

void KungFlowGame::OnJoyControllerAxisEvent(int joyControllerID, SDL_GameControllerAxis axisID, float value) {
  //if (fabs(value) > 0.5f) printf("axis: %i, %i, %f\n", hidControllerID, axisID, value);
  if (joyControllerID == 0) {

    // LS

    if (axisID == 0) joyInput.stick1.coords[0] = value;
    if (axisID == 1) joyInput.stick1.coords[1] = value;
    joyInput.stick1_processed = joyInput.stick1;

    // digital length
    bool normalize = false;
    float deadzoneInner = 0.5f;
    float deadzoneOuter = 0.1f;
    if (normalize) {
      if (joyInput.stick1_processed.GetLength() > deadzoneInner) {
        joyInput.stick1_processed.Normalize();
      } else {
        joyInput.stick1_processed = Vector2(0);
      }
    } else {
      if (joyInput.stick1_processed.GetLength() > deadzoneInner) {
        float length = joyInput.stick1_processed.GetLength();
        length = Clamp(length, deadzoneInner, (1.0f - deadzoneOuter));
        length -= deadzoneInner;
        length /= ((1.0f - deadzoneOuter) - deadzoneInner);
        joyInput.stick1_processed = joyInput.stick1_processed.GetNormalized(0) * length;
        //printf("%f\n", length);
      } else {
        joyInput.stick1_processed = Vector2(0);
      }
    }

    if (axisID == 0) joyInput.stick1.coords[0] = value;
    if (axisID == 1) joyInput.stick1.coords[1] = value;


    // RS

    if (axisID == 2) joyInput.stick2.coords[0] = value;
    if (axisID == 3) joyInput.stick2.coords[1] = value;
    joyInput.stick2_processed = joyInput.stick2;

    // digital length
    normalize = false;
    deadzoneInner = 0.5f;
    deadzoneOuter = 0.1f;
    if (normalize) {
      if (joyInput.stick2_processed.GetLength() > deadzoneInner) {
        joyInput.stick2_processed.Normalize();
      } else {
        joyInput.stick2_processed = Vector2(0);
      }
    } else {
      if (joyInput.stick2_processed.GetLength() > deadzoneInner) {
        float length = joyInput.stick2_processed.GetLength();
        length = Clamp(length, deadzoneInner, (1.0f - deadzoneOuter));
        length -= deadzoneInner;
        length /= ((1.0f - deadzoneOuter) - deadzoneInner);
        joyInput.stick2_processed = joyInput.stick2_processed.GetNormalized(0) * length;
        //printf("%f\n", length);
      } else {
        joyInput.stick2_processed = Vector2(0);
      }
    }

  }
}

void KungFlowGame::OnJoyControllerButtonEvent(int joyControllerID, int buttonID, bool pressedOrReleased) {
  //printf("button: %i, %i, %i\n", hidControllerID, buttonID, pressedOrReleased);
  if (joyControllerID == 0) {
    if (buttonID < 4) joyInput.buttons[buttonID] = pressedOrReleased;
  }
}

unsigned int KungFlowGame::GetAudioLatency_ms() {
  unsigned int blocksize;
  int numblocks;
  int sampleRate;
  float ms;
  FMOD_RESULT(FMOD_System_GetDSPBufferSize(GetAudioSystem(), &blocksize, &numblocks));
  FMOD_RESULT(FMOD_System_GetSoftwareFormat(GetAudioSystem(), &sampleRate, 0, 0));
  ms = (float)blocksize * 1000.0f / (float)sampleRate;
  return (unsigned int)ms;
}

// void KungFlowGame::OnStepPhysicsTime() {
//   if (GetActiveState() == playState) playState->SnapshotReplayObjects();
// }

int main(int argc, char *argv[]) {

  SDL_version compiled;
  SDL_version linked;
  SDL_VERSION(&compiled);
  SDL_GetVersion(&linked);
  printf("Compiled with SDL version %d.%d.%d\n", compiled.major, compiled.minor, compiled.patch);
  printf("Linked with SDL version %d.%d.%d\n", linked.major, linked.minor, linked.patch);

  bool vsync = true;
  int w = 1280; //3840
  int h = 720; //1024
  if (argc > 2) {
    w = atoi(argv[1]);
    h = atoi(argv[2]);
  }

  int renderContextResolutionMultiplier = 1;
  Init(w, h, vsync, e_WindowMode_Windowed, "KungFlow", renderContextResolutionMultiplier);
  //Init(3840, 1024, vsync, e_WindowMode_BorderlessFullscreen, "KungFlow");

  //int desiredGraphicsFPS = 1000;//120; // sane maximum to stop people with bad airflow from overheating their gear
  //int desiredGraphicsTimeStep_ms = 1000 / desiredGraphicsFPS;
  int desiredGraphicsTimeStep_ms = 0; // unlimited
  int physicsFPS = 300; // 300 is nice, because it is a multiple of both 50 (tv's) and 60 (most desktop monitors). and yes it is insanely much but who cares
  int physicsTimeStep_ms = 1000 / physicsFPS;

  Game *kungFlowGame = new KungFlowGame(desiredGraphicsTimeStep_ms, physicsTimeStep_ms);
  //kungFlowGame->SetMaximizedViewport(1.777777f);

  kungFlowGame->Run();

  delete kungFlowGame;

  Exit();

  return 0;
}

namespace boost {
  void assertion_failed(char const * expr, char const * function, char const * file, long line) {
    //printf("%s @ line %li: %s %s\n", file, line, function, expr);

    char errorString[256];
    sprintf(errorString, "%s @ line %li: %s %s\n", file, line, function, expr);
    //blunted::Log(blunted::e_FatalError, "boost", "assertion_failed", errorString);
    //exit(1);
  }
}
