#include "ScreenManager.h"

ScreenManager::ScreenManager(Adafruit_SSD1306 &display, SDCardManager *sdCardManager) : _displayRef(display)
, _viewNotFound{&(displayError), (ErrorInfo *)malloc(sizeof(ErrorInfo)), RESERVED_VIEW_UID, NULL, NULL}
, _viewFuncUndefined{&(displayError), (ErrorInfo *)malloc(sizeof(ErrorInfo)), RESERVED_VIEW_UID, NULL, NULL}
, _currentViewUndefined{&(displayError), (ErrorInfo *)malloc(sizeof(ErrorInfo)), RESERVED_VIEW_UID, NULL, NULL}
, _viewFunctionFailedToExecute{&(displayError), (ErrorInfo *)malloc(sizeof(ErrorInfo)), RESERVED_VIEW_UID, NULL, NULL}
,_sdCardManager(sdCardManager)
{  
  _viewLinkedList = (ViewLinkedList) createEmptyList();
  //We set the error messages :
  ((ErrorInfo *)_viewNotFound.pData)->errorMessage = FPSTR("View does not exist");
  ((ErrorInfo *)_viewFunctionFailedToExecute.pData)->errorMessage = FPSTR("View function failed\nto execute");
  ((ErrorInfo *)_viewFuncUndefined.pData)->errorMessage = FPSTR("View function is NULL");
}

boolean ScreenManager::init()
{
  return applyCfgFromSD();
}

boolean ScreenManager::applyCfgFromSD()
{
  if(_sdCardManager != NULL)
  {
    CFGFileParser cfgFileParser(*_sdCardManager, SCREEN_CFG_FILE);
    CFGDictionary<CFGParameterValue> *cfgDictionary = (CFGDictionary<CFGParameterValue> *) cfgFileParser.parseFile();

    if(cfgDictionary == NULL)
    {
      setDefault();
      return false;
    }

    if(cfgDictionary->get("ENABLED") != NULL)
      setEnabled(cfgDictionary->get("ENABLED")->booleanValue());
    
    if(cfgDictionary->get("DIMMED") != NULL)
      _displayRef.dim(cfgDictionary->get("DIMMED")->booleanValue());

    if(cfgDictionary->get("INVERTED") != NULL)
      _displayRef.invertDisplay(cfgDictionary->get("INVERTED")->booleanValue());

    if(cfgDictionary->get("ORIENTATION") != NULL)
      _displayRef.setRotation(orientationTranslator(cfgDictionary->get("ORIENTATION")->intValue()));
    else
      _displayRef.setRotation(OR_0);
    
    if(cfgDictionary->get("AUTO_OFF") != NULL)
      setAutoOFFDelay(cfgDictionary->get("AUTO_OFF")->uintValue());

    _displayRef.setTextColor(WHITE);
    delete cfgDictionary;
    return true;
  }
  else //Default values applied
  {
    setDefault();
    return true;
  }
}

void ScreenManager::setDefault()
{
  _displayRef.setRotation(OR_0);
  _displayRef.setTextColor(WHITE);
}

boolean ScreenManager::addView(boolean (*viewLogicFunction)(Adafruit_SSD1306&, void*), void *pData, const unsigned char UID)
{
  ViewLink viewLink = {viewLogicFunction, pData, UID, NULL, NULL};
  return addNewLinkAtTheEnd(&_viewLinkedList, viewLink);
}

void *ScreenManager::createEmptyList()
{
  return NULL;
}

boolean ScreenManager::addNewLinkAtTheEnd(ViewLinkedList *viewLinkedList, ViewLink viewLink)
{
  ViewLink *newViewLink = (ViewLink*) malloc(sizeof(ViewLink));
  if(newViewLink == NULL)
  {
    _error = MALLOC_FAILED;
    return false;
  }
  
  //Because of the const member
  memcpy(newViewLink, &viewLink, sizeof(*newViewLink));

  //If this is the first link
  if(isListEmpty(*viewLinkedList))
  {
    //The link list points to the first link (aka the head)
    *viewLinkedList = newViewLink;
    //The previous pointer is still NULL;
    //The _tail pointer is the same as the head one.
    _tail = *viewLinkedList;
  }
  else //If there are some links already
  {
    ViewLinkedList *cursor = viewLinkedList;
    ViewLink *previousLink = NULL;

    while(*cursor != NULL)
    {
      previousLink = *cursor;
      //We check if the UID already exists, if yes we replace the link
      if((*cursor)->UID == newViewLink->UID)
      {
        ViewLink *toDelete = *cursor;
        *cursor = newViewLink;
        newViewLink->next = toDelete->next;
        newViewLink->previous = toDelete->previous;
        //We check if the link as a next link registered
        if(toDelete->next != NULL)
        {
          //We update it's previous link since it's going to be deleted
          toDelete->next->previous = newViewLink;
        }
        //We do not forget to check if the link replaced is also the tail, in this case, we must update the tail
        if(_tail == toDelete)_tail = newViewLink;
        free(toDelete);

        return true;
      }
      cursor = &(*cursor)->next;
    }
    newViewLink->previous = previousLink;
    *cursor = newViewLink;
    //We need to update the tail
    _tail = newViewLink;
  }
  
  return true;
}

boolean ScreenManager::removeView(const unsigned char UID)
{
  return removeLinkByUID(&_viewLinkedList ,UID);
}

boolean ScreenManager::removeLinkByUID(ViewLinkedList *viewLinkedList, const unsigned char UID)
{
  if(isListEmpty(*viewLinkedList))return false;

  ViewLinkedList *cursor = viewLinkedList;

  while(*cursor != NULL)
  {
    //We check if this is the link we want to delete
    if((*cursor)->UID == UID)
    {
      ViewLink *toDelete = *cursor;
      *cursor = (*cursor)->next;

      if(toDelete->next != NULL)
      {
        //We update the previous link of the next member since it's going to be deleted
        toDelete->next->previous = toDelete->previous;
      }
      //We also update the tail if the link we delete was the tail
      if(_tail == toDelete)_tail = toDelete->previous;

      free(toDelete);
      return true;
    }
    cursor = &(*cursor)->next;
  }
  return false;
}

boolean ScreenManager::isListEmpty(ViewLinkedList viewLinkedList)
{
  return viewLinkedList == NULL;
}

ScreenManager::Error ScreenManager::getError() const
{
  return _error;
}

void ScreenManager::iterateThroughList()
{
  Serial.println("Lets go through");
  ViewLinkedList temp = _viewLinkedList;
  while(!isListEmpty(temp))
  {
    Serial.print("UID : ");Serial.println(temp->UID);
    temp = temp->next;
  }
}

ScreenManager::ViewLink* ScreenManager::getLinkByUID(ViewLinkedList viewLinkedList, const unsigned char UID)
{
  if(isListEmpty(viewLinkedList))
    return NULL;

  while(!isListEmpty(viewLinkedList))
  {
    if(viewLinkedList->UID == UID)return viewLinkedList;
    viewLinkedList = viewLinkedList->next;
  }

  return NULL;
}

void ScreenManager::run()
{
  if(!_enabled) return;
  //We handle the auto off logic here
  if(_autoOFFDelay)
  {
    if(millis() - _autoOFFDelayRef >= _autoOFFDelay)
    {
      sleep();
      return;
    }
  }

  if(millis() - _timeRef < _refreshInterval && !_forceRefresh) return;

  _timeRef = millis();
  
  if(((_error == OK || _error == VIEW_FAILED_TO_EXECUTE) || _forceRefresh) && _currentView != NO_CURRENT_VIEW)
  {
    _displayRef.clearDisplay();
    _displayRef.setCursor(0,0);
    _displayRef.setTextSize(1);

    switch(_error)
    {
      case OK:
      case VIEW_FAILED_TO_EXECUTE:
        if(!(*_currentView->viewLogicFunction)(_displayRef, _currentView->pData))
        {
          _displayRef.clearDisplay();
          //Error while executing the view function
          ((ErrorInfo *)_viewFunctionFailedToExecute.pData)->viewUID = _currentView->UID;
          (*_viewFunctionFailedToExecute.viewLogicFunction)(_displayRef, _viewFunctionFailedToExecute.pData);
        
          _error = VIEW_FAILED_TO_EXECUTE;
        }
        else
          _error = OK;
        break;
      case VIEW_NOT_FOUND:
          (*_viewNotFound.viewLogicFunction)(_displayRef, _viewNotFound.pData);
        break;
      case VIEW_FUNC_UNDEFINED:
          (*_viewFuncUndefined.viewLogicFunction)(_displayRef, _viewFuncUndefined.pData);
        break;
      default:
        break;
    }
    
    _displayRef.display();
    _forceRefresh = false;
  }
}

void ScreenManager::forceRefresh()
{
  if(!_enabled) return;
  _forceRefresh = true;
  run();
}

boolean ScreenManager::displayView(const uint8_t UID)
{
  if(!_enabled) return false;

  if(isSleeping())
    wakeUp();
  
   _autoOFFDelayRef = millis(); //Resets the auto off delay so the screen do not sleep on us.

  _forceRefresh = true;
  ViewLink *viewLink = getLinkByUID(_viewLinkedList, UID);
  
  if(viewLink == NULL)
  {
    //We display an error message on the screen
    ((ErrorInfo *)_viewNotFound.pData)->viewUID = UID;
    _error = VIEW_NOT_FOUND;
    return false;
  }
  else if(viewLink->viewLogicFunction == NULL)
  {
    //We display an error message on the screen
    ((ErrorInfo *)_viewFuncUndefined.pData)->viewUID = UID;
    _error = VIEW_FUNC_UNDEFINED;
    _currentView = viewLink;
    return false;
  }

  _currentView = viewLink;
  _error = OK;
  run();
  return _error == OK;
}

boolean ScreenManager::displayNextView()
{
  if(!_enabled) return false;

  if(isSleeping())
  {
    wakeUp();
    return true;
  }

  _autoOFFDelayRef = millis(); //Resets the auto off delay so the screen do not sleep on us.
    
  _forceRefresh = true;
  _error = OK;
  if(isListEmpty(_viewLinkedList))return false;

  if(_currentView == NO_CURRENT_VIEW)
  {
    _currentView = _viewLinkedList;
  }
  else if(!isListEmpty(_currentView->next))
  {
    _currentView = _currentView->next;
  }
  //End of the views, we cycle again :)
  else if(isListEmpty(_currentView->next))
  {
    _currentView = _viewLinkedList;
  }

  //We check if the view function is defined :
  if(_currentView != NO_CURRENT_VIEW)
  {
    if(_currentView->viewLogicFunction == NULL)
    {
      ((ErrorInfo *)_viewFuncUndefined.pData)->viewUID = _currentView->UID;
      _error = VIEW_FUNC_UNDEFINED;
    }
  }

  run();
  return _error == OK;
}

boolean ScreenManager::displayPreviousView()
{
  if(!_enabled) return false;

  if(isSleeping())
  {
    wakeUp();
    return true;
  }

  _autoOFFDelayRef = millis(); //Resets the auto off delay so the screen do not sleep on us.

  _forceRefresh = true;
  _error = OK;
  if(isListEmpty(_tail))return false;

  if(_currentView == NO_CURRENT_VIEW)
  {
    _currentView = _tail;
  }
  else if(!isListEmpty(_currentView->previous))
  {
    _currentView = _currentView->previous;
  }
  //End of the views, we cycle again :)
  else if(isListEmpty(_currentView->previous))
  {
    _currentView = _tail;
  }

  //We check if the view function is defined :
  if(_currentView != NO_CURRENT_VIEW)
  {
    if(_currentView->viewLogicFunction == NULL)
    {
      ((ErrorInfo *)_viewFuncUndefined.pData)->viewUID = _currentView->UID;
      _error = VIEW_FUNC_UNDEFINED;
    }
  }

  run();
  return _error == OK;
}

void ScreenManager::invertDisplayColor(const boolean inverted)
{
  _displayRef.invertDisplay(inverted);
  _displayColorInverted = inverted;
}

void ScreenManager::dimDisplay(const boolean dimmed)
{
  _displayRef.dim(dimmed);
  _displayDimmed = dimmed;
}

void ScreenManager::sleep()
{
  if(!_enabled || _sleeping)return;

  _sleeping = true;
  _displayRef.sleep();
}

void ScreenManager::wakeUp()
{
  if(!_enabled || !_sleeping)return;

  _sleeping  = false;
  _autoOFFDelayRef = millis();
  _displayRef.wakeUp();
}

void ScreenManager::orientDisplay(const Orientation orientation)
{
  _displayRef.setRotation(orientation);
}

boolean ScreenManager::isDisplayColorInverted() const
{
  return _displayColorInverted;
}

Orientation ScreenManager::getDisplayOrientation() const
{
  return (Orientation) _displayRef.getRotation();
}

boolean ScreenManager::isDisplayDimmed() const
{
  return _displayDimmed;
}

void ScreenManager::clearDisplay(const boolean bufferOnly)
{
  if(!_enabled) return;
  if(bufferOnly)
    _displayRef.clearDisplay();
  else
  {
    _displayRef.clearDisplay();
    _displayRef.display();
  }
}

void ScreenManager::clearViews()
{
  ViewLinkedList cursor = _viewLinkedList;
  while(!isListEmpty(cursor))
  {
    ViewLink *toDelete = cursor;
    cursor = cursor->next;
    free(toDelete);
  }
  //Do not forget to mark the list as empty
  _viewLinkedList = NULL;
  _tail = NULL;
}

void ScreenManager::setEnabled(boolean value)
{
  _enabled = value;
  if(value)
    _displayRef.wakeUp();
  else
    _displayRef.sleep();
}

void ScreenManager::setAutoOFFDelay(const uint64_t delay)
{
  _autoOFFDelay = delay;
}

boolean ScreenManager::isEnabled() const
{
  return _enabled;
}

boolean ScreenManager::isSleeping() const
{
  return _sleeping;
}

unsigned char ScreenManager::getViewCount()
{
  unsigned char counter = 0;
  ViewLinkedList temp = _viewLinkedList;
  while(!isListEmpty(temp))
  {
    counter++;
    temp = temp->next;
  }
  return counter;
}

int ScreenManager::getCurrentViewUID() const
{
  if(_currentView == NO_CURRENT_VIEW)
    return -1;
  return _currentView->UID < 0 ? -1 : _currentView->UID;
}

uint64_t ScreenManager::getAutoOFFDelay() const
{
  return _autoOFFDelay;
}

boolean ScreenManager::displayError(Adafruit_SSD1306 &display, void *pData)
{
  const ErrorInfo *errorInfo = (ErrorInfo *) pData;
  
  display.setTextSize(3);  
  display.setCursor(19,3);
  display.drawRect(0,0,display.width(), 27, WHITE);
  display.print(F("ERROR"));
  display.setTextSize(1);
  display.setCursor(0,29);
  display.print(errorInfo->errorMessage);
  //We then display the view UID that caused the probleme
  
  if(errorInfo->viewUID >= 0)
  {
    display.setCursor(0,56);
    char buff[30] = "";
    sprintf(buff,"VIEW UID : %d" ,errorInfo->viewUID);  
    display.println(buff);
  }
  
  return true;
}

const char* ScreenManager::getErrorMessage()const
{
  //NO_VIEW_ERROR, MALLOC_FAILED, VIEW_NOT_FOUND, VIEW_FUNC_UNDEFINED, VIEW_FAILED_TO_EXECUTE, CURRENT_VIEW_UNDEFINED
  switch(_error)
  {
    case OK:
      return "NO ERROR";
    case MALLOC_FAILED:
      return "MALLOC FAILED";
    case VIEW_NOT_FOUND:
      return "VIEW NOT FOUND";
    case VIEW_FUNC_UNDEFINED:
      return "VIEW FUNCTION UNDEFINED (NULL)";
    case VIEW_FAILED_TO_EXECUTE:
      return "VIEW FAILED TO EXECUTE (RETURNED FALSE)";
    case CURRENT_VIEW_UNDEFINED:
      return "CURRENT VIEW UNDEFINED";
    default :
      return "UNKNOWN ERROR";
  }
}

uint8_t ScreenManager::orientationTranslator(uint16_t degres)
{
  switch(degres)
  {
    case 0:
      return 2;
    case 90:
      return 3;
    case 180:
      return 0;
    case 270:
      return 1;
    default :
      return 2;
  }
}
