#include <PanasonicCKPHeatpumpIR.h>


PanasonicCKPHeatpumpIR::PanasonicCKPHeatpumpIR()
{
  static const char model[] PROGMEM = "panasonic_ckp";
  static const char info[]  PROGMEM = "{\"mdl\":\"panasonic_ckp\",\"dn\":\"Panasonic CKP\",\"mT\":16,\"xT\":30,\"fs\":6}";

  _model = model;
  _info = info;
}


// Panasonic CKP numeric values to command uint8_ts
void PanasonicCKPHeatpumpIR::send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd)
{
  // Sensible defaults for the heat pump mode

  uint8_t powerMode     = false;
  uint8_t operatingMode = PANASONIC_AIRCON1_MODE_KEEP;
  uint8_t fanSpeed      = PANASONIC_AIRCON1_FAN_AUTO;
  uint8_t temperature   = 23;
  uint8_t swingV        = PANASONIC_AIRCON1_VS_UP;
  uint8_t swingH        = PANASONIC_AIRCON1_HS_SWING;

  switch (powerModeCmd)
  {
    case POWER_ON:
      powerMode = true;
      break;
  }

  switch (operatingModeCmd)
  {
    case MODE_AUTO:
      operatingMode |= PANASONIC_AIRCON1_MODE_AUTO;
      break;
    case MODE_HEAT:
      operatingMode |= PANASONIC_AIRCON1_MODE_HEAT;
      break;
    case MODE_COOL:
      operatingMode |= PANASONIC_AIRCON1_MODE_COOL;
      break;
    case MODE_DRY:
      operatingMode |= PANASONIC_AIRCON1_MODE_DRY;
      break;
    case MODE_FAN:
      operatingMode |= PANASONIC_AIRCON1_MODE_FAN;
      temperatureCmd = 27; // Temperature is always 27 in FAN mode
      break;
    default:
      operatingMode |= PANASONIC_AIRCON1_MODE_HEAT;
      break;
  }

  switch (fanSpeedCmd)
  {
    case FAN_AUTO:
      fanSpeed = PANASONIC_AIRCON1_FAN_AUTO;
      break;
    case FAN_1:
      fanSpeed = PANASONIC_AIRCON1_FAN1;
      break;
    case FAN_2:
      fanSpeed = PANASONIC_AIRCON1_FAN2;
      break;
    case FAN_3:
      fanSpeed = PANASONIC_AIRCON1_FAN3;
      break;
    case FAN_4:
      fanSpeed = PANASONIC_AIRCON1_FAN4;
      break;
    case FAN_5:
      fanSpeed = PANASONIC_AIRCON1_FAN5;
      break;
  }

  if ( temperatureCmd > 15 && temperatureCmd < 31)
  {
    temperature = temperatureCmd;
  }

  switch (swingVCmd)
  {
    case VDIR_SWING:
      swingV = PANASONIC_AIRCON1_VS_SWING;
      break;
    case VDIR_UP:
      swingV = PANASONIC_AIRCON1_VS_UP;
      break;
    case VDIR_MUP:
      swingV = PANASONIC_AIRCON1_VS_MUP;
      break;
    case VDIR_MIDDLE:
      swingV = PANASONIC_AIRCON1_VS_MIDDLE;
      break;
    case VDIR_MDOWN:
      swingV = PANASONIC_AIRCON1_VS_MDOWN;
      break;
    case VDIR_DOWN:
      swingV = PANASONIC_AIRCON1_VS_DOWN;
      break;
  }

  switch (swingHCmd)
  {
    case HDIR_SWING:
      swingH = PANASONIC_AIRCON1_HS_SWING;
      break;
    case HDIR_AUTO: // Well, just set it to manual
      swingH = PANASONIC_AIRCON1_HS_MANUAL;
      break;
  }

  sendPanasonicCKP(IR, operatingMode, fanSpeed, temperature, swingV, swingH);
  delay(1000); // Sleep 1 second between the messages

  // This will change the power state in one minute from now
  sendPanasonicCKPOnOffTimerCancel(IR, powerMode, false);
/*
  // Send the 'timer cancel' signal 2 minutes later
  if (panasonicCancelTimer != 0)
  {
    timer.stop(panasonicCancelTimer);
    panasonicCancelTimer = 0;
  }

  // Note that the argument to 'timer.after' has to be explicitly cast into 'long'
  panasonicCancelTimer = timer.after(2L*60L*1000L, sendPanasonicCKPCancelTimer);

  LOG(F("'Timer cancel' timer ID: "));
  LOGLN(panasonicCancelTimer);
*/
}

// Send the Panasonic CKP code
void PanasonicCKPHeatpumpIR::sendPanasonicCKP(IRSender& IR, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature, uint8_t swingV, uint8_t swingH)
{
  uint8_t sendBuffer[4];

  // Fan speed & temperature, temperature needs to be 27 in FAN mode
  if (operatingMode == PANASONIC_AIRCON1_MODE_FAN || operatingMode == (PANASONIC_AIRCON1_MODE_FAN | PANASONIC_AIRCON1_MODE_KEEP ))
  {
    temperature = 27;
  }

  sendBuffer[0] = fanSpeed | (temperature - 15);

  // Power toggle & operation mode
  sendBuffer[1] = operatingMode;

  // Swings
  sendBuffer[2] = swingV | swingH;

  // Always 0x36
  sendBuffer[3]  = 0x36;

  // Send the code
  sendPanasonicCKPraw(IR, sendBuffer);
}

// Send the Panasonic CKP raw code
void PanasonicCKPHeatpumpIR::sendPanasonicCKPraw(IRSender& IR, uint8_t sendBuffer[])
{
  // 40 kHz PWM frequency
  IR.setFrequency(38);

  // Header, two first uint8_ts repeated
  IR.mark(PANASONIC_AIRCON1_HDR_MARK);
  IR.space(PANASONIC_AIRCON1_HDR_SPACE);

  for (int i=0; i<2; i++) {
    IR.sendIRbyte(sendBuffer[0], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);
    IR.sendIRbyte(sendBuffer[0], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);
    IR.sendIRbyte(sendBuffer[1], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);
    IR.sendIRbyte(sendBuffer[1], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);

    IR.mark(PANASONIC_AIRCON1_HDR_MARK);
    IR.space(PANASONIC_AIRCON1_HDR_SPACE);
  }

  // Pause

  IR.mark(PANASONIC_AIRCON1_BIT_MARK);
  IR.space(PANASONIC_AIRCON1_MSG_SPACE);

  // Header, two last uint8_ts repeated

  IR.mark(PANASONIC_AIRCON1_HDR_MARK);
  IR.space(PANASONIC_AIRCON1_HDR_SPACE);

  for (int i=0; i<2; i++) {
    IR.sendIRbyte(sendBuffer[2], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);
    IR.sendIRbyte(sendBuffer[2], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);
    IR.sendIRbyte(sendBuffer[3], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);
    IR.sendIRbyte(sendBuffer[3], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);

    IR.mark(PANASONIC_AIRCON1_HDR_MARK);
    IR.space(PANASONIC_AIRCON1_HDR_SPACE);
  }

  IR.mark(PANASONIC_AIRCON1_BIT_MARK);
  IR.space(0);
}

// Send the Panasonic CKP On/Off code
//
// CKP does not have discrete ON/OFF commands, but this can be emulated by using the timer
// The side-effects of using the timer are:
// * ONE minute delay before the power state changes
// * the 'TIMER' led (orange) is lit
// * a timer event is scheduled to cancel the timer after TWO minutes (the 'TIMER' led turns off
void PanasonicCKPHeatpumpIR::sendPanasonicCKPOnOffTimerCancel(IRSender& IR, boolean powerState, boolean cancelTimer)
{
  static const uint8_t ON_msg[] PROGMEM =     { 0x7F, 0x38, 0xBF, 0x38, 0x10, 0x3D, 0x80, 0x3D, 0x09, 0x34, 0x80, 0x34 }; //  ON at 00:10, time now 00:09, no OFF timing
  static const uint8_t OFF_msg[] PROGMEM =    { 0x10, 0x38, 0x80, 0x38, 0x7F, 0x3D, 0xBF, 0x3D, 0x09, 0x34, 0x80, 0x34 }; // OFF at 00:10, time now 00:09, no ON timing
  static const uint8_t CANCEL_msg[] PROGMEM = { 0x7F, 0x38, 0xBF, 0x38, 0x7F, 0x3D, 0xBF, 0x3D, 0x17, 0x34, 0x80, 0x34 }; // Timer CANCEL

  // Save some SRAM by only having one copy of the template on the SRAM
  uint8_t sendBuffer[sizeof(ON_msg)];

  if ( cancelTimer == true ) {
    memcpy_P(sendBuffer, CANCEL_msg, sizeof(ON_msg));
  } else if ( powerState == true ) {
    memcpy_P(sendBuffer, ON_msg, sizeof(ON_msg));
  } else {
    memcpy_P(sendBuffer, OFF_msg, sizeof(ON_msg));
  }

  // 40 kHz PWM frequency
  IR.setFrequency(38);

  for (int i=0; i<6; i++) {
    IR.mark(PANASONIC_AIRCON1_HDR_MARK);
    IR.space(PANASONIC_AIRCON1_HDR_SPACE);

    IR.sendIRbyte(sendBuffer[i*2 + 0], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);
    IR.sendIRbyte(sendBuffer[i*2 + 0], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);
    IR.sendIRbyte(sendBuffer[i*2 + 1], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);
    IR.sendIRbyte(sendBuffer[i*2 + 1], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE);

    IR.mark(PANASONIC_AIRCON1_HDR_MARK);
    IR.space(PANASONIC_AIRCON1_HDR_SPACE);

    if ( i < 5 ) {
      IR.mark(PANASONIC_AIRCON1_BIT_MARK);
      IR.space(PANASONIC_AIRCON1_MSG_SPACE);
    }
  }

  IR.mark(PANASONIC_AIRCON1_BIT_MARK);
  IR.space(0);
}

// Send the Panasonic CKP timer cancel
void PanasonicCKPHeatpumpIR::sendPanasonicCKPCancelTimer(IRSender& IR)
{
  LOGLN(F("Sending Panasonic CKP timer cancel"));

  sendPanasonicCKPOnOffTimerCancel(IR, false, true);
}

