#ifndef _MeleeStatData_H_
#define _MeleeStatData_H_

#define PLAYER_COUNT 2
#define STOCK_COUNT 4
#define COMBO_STRING_BUFFER_SIZE 200
#define RECOVERY_BUFFER_SIZE 100
#define PUNISH_BUFFER_SIZE 150
#define MAX_FRAMES 28800
#define MSG_BUFFER_SIZE 1024

#define JOYSTICK_NE 1
#define JOYSTICK_SE 2
#define JOYSTICK_SW 3
#define JOYSTICK_NW 4
#define JOYSTICK_N 5
#define JOYSTICK_E 6
#define JOYSTICK_S 7
#define JOYSTICK_W 8
#define JOYSTICK_DZ 9

//For statistics
#define FRAMES_LANDED_RECOVERY 45
#define FRAMES_LANDED_PUNISH 45
#define COMBO_STRING_TIMEOUT 45

//Animation ID ranges
#define DAMAGE_START 0x4B
#define DAMAGE_END 0x5B
#define CAPTURE_START 0xDF
#define CAPTURE_END 0xE8
#define GUARD_START 0xB2
#define GUARD_END 0xB6
#define GROUNDED_CONTROL_START 0xE
#define GROUNDED_CONTROL_END 0x18
#define TECH_START 0xC7
#define TECH_END 0xCC
#define DYING_START 0x0
#define DYING_END 0xA

//Animation ID specific
#define ROLL_FORWARD 0xE9
#define ROLL_BACKWARD 0xEA
#define SPOT_DODGE 0xEB
#define AIR_DODGE 0xEC
#define ACTION_WAIT 0xE
#define ACTION_DASH 0x14
#define ACTION_KNEE_BEND 0x18
#define GUARD_ON 0xB2
#define TECH_MISS_UP 0xB7
#define TECH_MISS_DOWN 0xBF

//Stage IDs
#define FOD 2
#define STADIUM 3
#define YOSHIS 8
#define DREAM_LAND 28
#define BATTLEFIELD 31
#define FD 32

typedef struct {
    //Volatile data - this data will change throughout the course of the match
    uint8_t internalCharacterId;
    uint16_t animation;
    float locationX;
    float locationY;
    uint8_t stocks;
    float percent;
    float shieldSize;
    uint8_t lastMoveHitId;
    uint8_t comboCount;
    uint8_t lastHitBy;

    //Controller information
    float joystickX;
    float joystickY;
    float cstickX;
    float cstickY;
    float trigger;
    uint32_t buttons; //This will include multiple "buttons" pressed on special buttons. For example I think pressing z sets 3 bits

    //This is extra controller information
    uint16_t physicalButtons; //A better representation of what a player is actually pressing
    float lTrigger;
    float rTrigger;
    } PlayerFrameData;
    
typedef struct {
  //Recovery
  float recoveryStartPercent;
  uint32_t recoveryStartFrame;
  int isRecovering;
  int isHitOffStage;
  int isLandedOnStage;
  uint8_t framesSinceLanding;
  
  //Combo String
  float stringStartPercent;
  uint32_t stringStartFrame;
  uint16_t stringCount;
  uint8_t stringResetCounter;

  //Punishes
  int isPunishing;
  uint8_t framesSincePunishReset;
  float punishStartPercent;
  uint32_t punishStartFrame;
  uint16_t punishHitCount;

  //Misc
  uint32_t framesWithoutDamage;
} PlayerFlags;

typedef struct {
	uint32_t frameStart;
  	uint32_t frameEnd;
	float percent;
	uint8_t lastHitBy;
	uint16_t lastAnimation;
  
  	//Combo String
  	uint16_t killedInOpenings;
} StockStatistics;

typedef struct {
  uint32_t frameStart;
  uint32_t frameEnd;
  float percentStart;
  float percentEnd;
  uint16_t hitCount;
} ComboString;

typedef struct {
  uint32_t frameStart;
  uint32_t frameEnd;
  float percentStart;
  float percentEnd;
  int isSuccessful;
} Recovery;

typedef struct {
  uint32_t frameStart;
  uint32_t frameEnd;
  float percentStart;
  float percentEnd;
  uint16_t hitCount;
  int isKill;
} Punish;

typedef struct {
  //Internal Character Usage - currenly only used for sheik/zelda detection
  uint32_t internalCharUsage[33];
  
  //Positional
  uint32_t framesAboveOthers; //Assuming if you are higher, you are in a worse position
  uint32_t framesClosestCenter; //Assuming if you are closer to center, you are controlling the stage
  float averageDistanceFromCenter;
  
  uint32_t framesInShield; //Amount of frames spent shielding
  uint32_t mostFramesWithoutDamage; //Amount of frames without being hit
  
  //Defensive option selection
  uint16_t rollCount;
  uint16_t spotDodgeCount;
  uint16_t airDodgeCount;

  //Tech stats - to be implemented
  uint16_t techMissCount;
  uint16_t techLeftCount;
  uint16_t techRightCount;
  uint16_t techPlaceCount;
  
  //APM
  uint16_t actionCount;
  float actionsPerMinute;

  uint16_t numberOfOpenings; //this is the number of times a player started a combo string
  float openingsPerKill;
  
  uint8_t comboStringIndex;
  float avgComboDmg;
  float avgHitsPerCombo;
  uint8_t recoveryIndex;
  float edgeguardEfficiency;
  
  uint8_t punishIndex;
  float neutralWins;
  
  float avgDeathPercent;
  
  StockStatistics stocks[STOCK_COUNT];
  ComboString comboStrings[COMBO_STRING_BUFFER_SIZE];
  Recovery recoveries[RECOVERY_BUFFER_SIZE];
  Punish punishes[PUNISH_BUFFER_SIZE];
} PlayerStatistics;

typedef struct {
  //Static data
  uint8_t characterId;
  uint8_t characterColor;
  uint8_t playerType;
  uint8_t controllerPort;

  //Update data
  PlayerFrameData currentFrameData;

  //Used for statistic calculation
  PlayerFrameData previousFrameData;
  PlayerFlags flags;
  PlayerStatistics stats;
} Player;

typedef struct {
  Player players[PLAYER_COUNT]; //Contains all information relevant to individual players
  uint16_t stage; //Stage ID

  //Fromt Update event
  uint32_t frameCounter; //Frame count
  uint32_t framesMissed;
  uint32_t randomSeed;
  
  //From OnGameEnd event
  uint8_t winCondition;
} Game;

int checkIfOffStage(uint16_t stage, float x, float y) {
  //Checks if player is off stage. These are the edge coordinates +5
  switch(stage) {
    case 0x02:
      return x < -68.35 || x > 68.35 || y < -10;
    case 0x03:
      return x < -92.75 || x > 92.75 || y < -10;
    case 0x08:
      return x < -61 || x > 61 || y < -10;
    case 0x1c:
      return x < -82.27 || x > 82.27 || y < -10;
    case 0x1f:
      return x < -73.4 || x > 73.4 || y < -10;
    case 0x20:
      return x < -90.5606 || x > 90.5606 || y < -10;
    default:
      return 0;
  }
}

//Return joystick region
uint8_t getJoystickRegion(float x, float y) {
  if(x >= 0.2875 && y >= 0.2875) return JOYSTICK_NE;
  else if(x >= 0.2875 && y <= -0.2875) return JOYSTICK_SE; 
  else if(x <= -0.2875 && y <= -0.2875) return JOYSTICK_SW;
  else if(x <= -0.2875 && y >= 0.2875) return JOYSTICK_NW;
  else if(y >= 0.2875) return JOYSTICK_N;
  else if(x >= 0.2875) return JOYSTICK_E;
  else if(y <= -0.2875) return JOYSTICK_S;
  else if(x <= -0.2875) return JOYSTICK_W;
  else return 0;
}

Player appendRecovery(int successfulRecovery, Player cp, uint32_t frameCounter) {
  Recovery r =  cp.stats.recoveries[cp.stats.recoveryIndex];
  r.frameStart = cp.flags.recoveryStartFrame;
  r.frameEnd = frameCounter;
  r.percentStart = cp.flags.recoveryStartPercent;
  r.percentEnd = cp.previousFrameData.percent;
  r.isSuccessful = successfulRecovery;
  
  cp.stats.recoveries[cp.stats.recoveryIndex] = r;
  
  if (cp.stats.recoveryIndex < RECOVERY_BUFFER_SIZE - 1) {
     cp.stats.recoveryIndex++;
  }
  
  return cp;
}

PlayerFlags resetRecoveryFlags(PlayerFlags flags) {
  flags.isRecovering = 0;
  flags.isHitOffStage = 0;
  flags.isLandedOnStage = 0;
  flags.framesSinceLanding = 0;
  return flags;
}

Player appendPunish(int isKill, Player cp, Player op, uint32_t frameCounter) {
  Punish p =  cp.stats.punishes[cp.stats.punishIndex];
  p.frameStart = cp.flags.punishStartFrame;
  p.frameEnd = frameCounter;
  p.percentStart = cp.flags.punishStartPercent;
  p.percentEnd = op.previousFrameData.percent;
  p.hitCount = cp.flags.punishHitCount;
  p.isKill = isKill;
  
  cp.stats.punishes[cp.stats.punishIndex] = p;
  
  if (cp.stats.punishIndex < PUNISH_BUFFER_SIZE - 1) {
     cp.stats.punishIndex++;
  }
  
  return cp;
}

PlayerFlags resetPunishFlags(PlayerFlags flags) {
  flags.isPunishing = 0;
  flags.framesSincePunishReset = 0;
  flags.punishHitCount = 0;
  return flags;
}


#endif