/*
 * This file is part of the "bluetoothheater" distribution 
 * (https://gitlab.com/mrjones.id.au/bluetoothheater) 
 *
 * Copyright (C) 2018  Ray Jones <ray@mrjones.id.au>
 *
 * 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 3 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, see <https://www.gnu.org/licenses/>.
 * 
 */

#include "128x64OLED.h"
#include "fonts/MiniFont.h"
#include "fonts/Icons.h"
#include "../Bluetooth/BluetoothAbstract.h" 
#include "DetailedScreen.h"
#include "../Wifi/BTCWifi.h"
#include "KeyPad.h"
#include "../Protocol/helpers.h"
#include "../Protocol/Protocol.h"
#include "../Utility/NVStorage.h"


#define MINIFONT miniFontInfo

#define X_FAN_ICON     55 
#define Y_FAN_ICON     39
#define X_FUEL_ICON    81 
#define Y_FUEL_ICON    39
#define X_TARGET_ICON  31
#define Y_TARGET_ICON  39
#define Y_BASELINE     58
#define X_GLOW_ICON    97
#define Y_GLOW_ICON    38
#define X_BODY_BULB   119
#define X_BULB          1  // >= 1
#define Y_BULB          4

#define MINI_TEMPLABEL
#define MINI_TARGETLABEL
#define MINI_FANLABEL
#define MINI_GLOWLABEL
#define MINI_FUELLABEL
#define MINI_BODYLABEL

///////////////////////////////////////////////////////////////////////////
//
// CDetailedScreen
//
// This screen provides a detailed control/status function
//
///////////////////////////////////////////////////////////////////////////

CDetailedScreen::CDetailedScreen(C128x64_OLED& display, CScreenManager& mgr) : CScreenHeader(display, mgr) 
{
  _animatePump = false;
  _animateRPM = false;
  _animateGlow = false;
  _fanAnimationState = 0;
  _dripAnimationState = 0;
  _heatAnimationState = 0;
  _keyRepeatCount = -1;
  _showTarget = 0;
}


bool 
CDetailedScreen::show()
{
  CScreenHeader::show();

  const char* c = String(getTemperatureSensor()).c_str();
  
  int runstate = getHeaterInfo().getRunStateEx();
  int errstate = getHeaterInfo().getErrState(); 
  if(errstate) errstate--;  // correct for +1 biased return value
  
  long tDelta = millis() - _showTarget;
  if(_showTarget && (tDelta > 0)) {
    _showTarget = 0;
  }

  float desiredT = 0;
  if((runstate && (runstate <= 5)) || _showTarget) {
    if(getThermostatModeActive())
      desiredT = getTemperatureDesired();
    else
      desiredT = -getHeaterInfo().getPump_Fixed();
  }

  float fTemp = getTemperatureSensor();
  showThermometer(desiredT,    // read values from most recently sent [BTC] frame
                  fTemp);

  _animateRPM = false;
  _animatePump = false;
  _animateGlow = false;

  if(runstate != 0 && runstate != 10) {  // not idle modes
    float power = getHeaterInfo().getGlowPlug_Power();
    if(power > 1) {
      showGlowPlug(power);
    }

    if(_showTarget)
      showFanV(getHeaterInfo().getFan_Voltage());
    else
      showFan(getHeaterInfo().getFan_Actual());

    showFuel(getHeaterInfo().getPump_Actual());

    showBodyThermometer(getHeaterInfo().getTemperature_HeatExchg());
  }

  showRunState(runstate, errstate);
  return true;
}


bool 
CDetailedScreen::animate()
{
  bool retval = CScreenHeader::animate();

  if(_animatePump || _animateRPM || _animateGlow) {

    if(_animatePump) {
      // erase region of fuel icon
      _display.fillRect(X_FUEL_ICON, Y_FUEL_ICON, W_FUEL_ICON, H_FUEL_ICON + 4, BLACK);
      _display.drawBitmap(X_FUEL_ICON, Y_FUEL_ICON+(_dripAnimationState/2), FuelIcon, W_FUEL_ICON, H_FUEL_ICON, WHITE);
      _dripAnimationState++;
      _dripAnimationState &= 0x07;
    }

    if(_animateRPM) {
      // erase region of fuel icon
      _display.fillRect(X_FAN_ICON, Y_FAN_ICON, W_FAN_ICON, H_FAN_ICON, BLACK);
      switch(_fanAnimationState) {
        case 0: _display.drawBitmap(X_FAN_ICON, Y_FAN_ICON, FanIcon1, W_FAN_ICON, H_FAN_ICON, WHITE); break;
        case 1: _display.drawBitmap(X_FAN_ICON, Y_FAN_ICON, FanIcon2, W_FAN_ICON, H_FAN_ICON, WHITE); break;
        case 2: _display.drawBitmap(X_FAN_ICON, Y_FAN_ICON, FanIcon3, W_FAN_ICON, H_FAN_ICON, WHITE); break;
        case 3: _display.drawBitmap(X_FAN_ICON, Y_FAN_ICON, FanIcon4, W_FAN_ICON, H_FAN_ICON, WHITE); break;
      }
      _fanAnimationState++;
      _fanAnimationState &= 0x03;
    }
    
    if(_animateGlow) {
      _display.fillRect(X_GLOW_ICON, Y_GLOW_ICON, 17, 10, BLACK);
      _display.drawBitmap(X_GLOW_ICON, Y_GLOW_ICON, GlowPlugIcon, 16, 9, WHITE);
      _display.drawBitmap(X_GLOW_ICON, Y_GLOW_ICON + 2 + _heatAnimationState, GlowHeatIcon, 17, 2, WHITE);
      _heatAnimationState -= 2;
      _heatAnimationState &= 0x07;
    }

    retval = true;
  }
  retval |= CScreen::animate();
  return retval;
}


bool 
CDetailedScreen::keyHandler(uint8_t event)
{
  
  if(event & keyPressed) {
    _keyRepeatCount = 0;     // unlock tracking of repeat events
  }
  // require hold to turn ON or OFF
  if(event & keyRepeat) {
    if(_keyRepeatCount >= 0) {
      _keyRepeatCount++;
      // hold LEFT to toggle GPIO output #1
      if(event & key_Left) {
        if(_keyRepeatCount > 2) {
          _keyRepeatCount = -1;     // prevent double handling
          setGPIO(0, !getGPIO(0));  // toggle GPIO output #1
        }
      }
      // hold RIGHT to toggle GPIO output #2
      if(event & key_Right) {
        if(_keyRepeatCount > 2) {
          _keyRepeatCount = -1;     // prevent double handling
          setGPIO(1, !getGPIO(1));  // toggle GPIO output #2
        }
      }

      if(event & key_Centre) {
        int runstate = getHeaterInfo().getRunStateEx();
        if(runstate) {   // running, including cyclic mode idle
          if(_keyRepeatCount > 5) {
            _keyRepeatCount = -1;        // prevent double handling
            requestOff();
          }
        }
        else {
          if(_keyRepeatCount > 3) {
            _keyRepeatCount = -1;   // prevent double handling
            requestOn();
          }
        }
      }
      if(event & key_Down) {
        if(_keyRepeatCount > 1) {    // held Down - togle thermo/fixed mode
          _keyRepeatCount = -1;      // prevent double handling
          if(reqThermoToggle()) {
            _showTarget = millis() + 3500;
            NVstore.save();
          } 
          else  _reqOEMWarning();
        }
      }
      if(event & key_Up) {
        if(_keyRepeatCount > 1) {    // held Down - togle thermo/fixed mode
          _keyRepeatCount = -1;      // prevent double handling
          NVstore.setDegFMode(NVstore.getDegFMode() ? 0 : 1);
        }
      }
    }
  }
  // release event
  if(event & keyReleased) {
    if(_keyRepeatCount == 0) {  // short Up press - lower target
      if(event & key_Up) {
        if(reqTempDelta(+1))  _showTarget = millis() + 3500;
        else  _reqOEMWarning();
      }
      if(event & key_Down) {   // short Down press - lower target
        if(reqTempDelta(-1))  _showTarget = millis() + 3500;
        else  _reqOEMWarning();
      }
      if(event & key_Centre) {  // short Centre press - show target
        _showTarget = millis() + 3500;
      }
      _ScreenManager.reqUpdate();
      if(event & key_Left) {
        _ScreenManager.prevMenu();
      }
      if(event & key_Right) {
        _ScreenManager.nextMenu();
      }
    }
    _keyRepeatCount = -1;
  }

  return true;
}


#define TEMP_YPOS(A) ((20 - int(A)) + 27)  // 26 is location of 20deg tick
void 
CDetailedScreen::showThermometer(float desired, float actual) 
{
  char msg[16];
  // draw bulb design
  _display.drawBitmap(X_BULB, Y_BULB, ambientThermometerIcon, W_BULB_ICON, H_BULB_ICON, WHITE);

  if(actual > 0) { 
    // draw mercury
    int yPos = Y_BULB + TEMP_YPOS(actual);
    _display.drawLine(X_BULB + 3, yPos, X_BULB + 3, Y_BULB + 42, WHITE);
    _display.drawLine(X_BULB + 4, yPos, X_BULB + 4, Y_BULB + 42, WHITE);  
  }
  // print actual temperature
  if(actual > -80) {
#ifdef MINI_TEMPLABEL  
    CTransientFont AF(_display, &MINIFONT);  // temporarily use a mini font
    if(NVstore.getDegFMode()) {
      actual = actual * 9 / 5 + 32;
      sprintf(msg, "%.1f`F", actual);
    }
    else {
      sprintf(msg, "%.1f`C", actual);
    }
#else
    sprintf(msg, "%.1f", actual);
#endif
    _printMenuText(0, Y_BASELINE, msg);
  }
  else {
    _printInverted(1, Y_BASELINE-2, "N/A", true);
  }

  // draw target setting
  if(desired) {
    _display.drawBitmap(X_TARGET_ICON, Y_TARGET_ICON, TargetIcon, W_TARGET_ICON, H_TARGET_ICON, WHITE);   // set indicator against bulb
    char msg[16];
    if(desired > 0) {
      int yPos = Y_BULB + TEMP_YPOS(desired) - 2;
      _display.drawBitmap(X_BULB-1, yPos, thermoPtr, 3, 5, WHITE);   // set indicator against bulb
      if(NVstore.getDegFMode()) {
        desired = desired * 9 / 5 + 32;
        sprintf(msg, "%.0f`F", desired);
      }
      else {
        sprintf(msg, "%.0f`C", desired);
      }
    }
    else {
      sprintf(msg, "%.1fHz", -desired);
    }
#ifdef MINI_TARGETLABEL
    CTransientFont AF(_display, &MINIFONT);  // temporarily use a mini font
#endif
    _printMenuText(X_TARGET_ICON + (W_TARGET_ICON/2), Y_BASELINE, msg, false, eCentreJustify);
  }
}

#define BODY_YPOS(A) ((((100 - A) * 3) / 16) + 22)   // 100degC centre - ticks +- 80C
void 
CDetailedScreen::showBodyThermometer(int actual) 
{
  // draw bulb design
  _display.drawBitmap(X_BODY_BULB, Y_BULB, bodyThermometerIcon, 8, 50, WHITE);
  // draw mercury
  int yPos = Y_BULB + BODY_YPOS(actual);
  _display.drawLine(X_BODY_BULB + 3, yPos, X_BODY_BULB + 3, Y_BULB + 42, WHITE);
  _display.drawLine(X_BODY_BULB + 4, yPos, X_BODY_BULB + 4, Y_BULB + 42, WHITE);
  // print actual temperature
  _display.setTextColor(WHITE);
  char label[16];
  // determine width and position right justified
#ifdef MINI_BODYLABEL
  CTransientFont AF(_display, &MINIFONT);  // temporarily use a mini font
  if(NVstore.getDegFMode()) {
    actual = actual * 9 / 5 + 32;
    sprintf(label, "%d`F", actual);
  }
  else {
    sprintf(label, "%d`C", actual);
  }
#else
  sprintf(label, "%d", actual);
#endif
  _printMenuText(_display.width(), Y_BASELINE, label, false, eRightJustify);
}


void 
CDetailedScreen::showGlowPlug(float power)
{
  _display.drawBitmap(X_GLOW_ICON, Y_GLOW_ICON, GlowPlugIcon, W_GLOW_ICON, H_GLOW_ICON, WHITE);
//  _animateGlow = true;
  char msg[16];
  sprintf(msg, "%.0fW", power);
#ifdef MINI_GLOWLABEL  
  CTransientFont AF(_display, &MINIFONT);  // temporarily use a mini font
#endif
  _printMenuText(X_GLOW_ICON + (W_GLOW_ICON/2), 
                Y_GLOW_ICON + H_GLOW_ICON + 3,
                msg, false, eCentreJustify);
}

void 
CDetailedScreen::showFan(int RPM)
{
  // NOTE: fan rotation animation performed in animateOLED
  _animateRPM = RPM != 0;   // used by animation routine

  _display.setTextColor(WHITE);
  char msg[16];
  sprintf(msg, "%d", RPM);
#ifdef MINI_FANLABEL  
  CTransientFont AF(_display, &MINIFONT);  // temporarily use a mini font
#endif
  _printMenuText(X_FAN_ICON + (W_FAN_ICON/2), Y_BASELINE, msg, false, eCentreJustify);
}

void 
CDetailedScreen::showFanV(float volts)
{
  // NOTE: fan rotation animation performed in animateOLED
  _animateRPM = volts != 0;   // used by animation routine

  _display.setTextColor(WHITE);
  char msg[16];
  sprintf(msg, "%.1fV", volts);
#ifdef MINI_FANLABEL  
  CTransientFont AF(_display, &MINIFONT);  // temporarily use a mini font
#endif
  _printMenuText(X_FAN_ICON + (W_FAN_ICON/2), Y_BASELINE, msg, false, eCentreJustify);
}

void 
CDetailedScreen::showFuel(float rate)
{
  // NOTE: fuel drop animation performed in animateOLED
  _animatePump = rate != 0;    // used by animation routine
  if(rate) {
    char msg[16];
    sprintf(msg, "%.1f", rate);
#ifdef MINI_FUELLABEL
    CTransientFont AF(_display, &MINIFONT);  // temporarily use a mini font
#endif
    _printMenuText(X_FUEL_ICON + (W_FUEL_ICON/2), Y_BASELINE, msg, false, eCentreJustify);
  }
}

void 
CDetailedScreen::showRunState(int runstate, int errstate) 
{
  static bool toggle = false;
  const char* toPrint = NULL;
  int yPos = 25;
  _display.setTextColor(WHITE, BLACK);
  if(errstate && ((runstate == 0) || (runstate > 5))) {
    // an error is present in idle or states beyond running, show it
    // create an "E-XX" message to display
    char msg[16];
    sprintf(msg, "E-%02d", errstate);
    if(runstate > 5)
      yPos -= _display.textHeight();
    _display.setCursor(_display.xCentre(), yPos);
    // flash error code
    toggle = !toggle;
    if(toggle)
      _display.printCentreJustified(msg);
    else {
      _display.printCentreJustified("          ");
    }
    yPos += _display.textHeight();
    toPrint = getHeaterInfo().getErrStateStr();
  }
  else {
    // no errors, heater normal
    toPrint = getHeaterInfo().getRunStateStr();
  }
  if(toPrint) {
    _printMenuText(_display.xCentre(), yPos, toPrint, false, eCentreJustify);
  }
}



