#include "LedDisplayController.h"

// --------------------
// - Public functions -
// --------------------

void LedDisplayController::loop()
{
    //Serial.printf("Ligth: %d \n" , analogRead(36));
    storeDataToEEPROM();
    if (this->disp_show)
    {
        measureBrightnessEnv();
        disp_start_set();
        if (true == text_sets.sets[text_curr_nr].active)
        {
            if (text_sets.sets[text_curr_nr].text[0] != '\0')
            {
                if (text_sets.sets[text_curr_nr].scroll)
                {
                    disp_scroll_text();
                }
            }
        }
    }
}

bool LedDisplayController::storeDataToEEPROM()
{

    if(millis() - this->setsModiefiedLastTime_ms > this->msToWaitBeforeModifyGetsInactive)
    {
        if(true == this->setModifyingActive)
        {
            this->setModifyingActive = false;
            Serial.println("Sets are not longer modified by external data ...");
            Serial.println("Storing Text Sets To EEPROM now...");
            if (this->eepromUnit == nullptr)
            {
                Serial.println("ERROR");
                return false;
            }

            strncpy(this->text_sets.valid, "OK", sizeof(this->text_sets.valid));
            this->eepromUnit->write(this->text_sets);
            Serial.println("OK");
            return true;
        }
    }
    return true;
}

bool LedDisplayController::registerEepromUnit(EepromManager *eepromManager)
{
    if (this->eepromUnit != nullptr)
        return false;

    this->eepromUnit = eepromManager->registerEempromUnit(sizeof(sets_t));
    return this->loadTextSets();
}

String LedDisplayController::getTextSetParameter(int index, DisplayTextSetParameter parameter)
{
    if (index < 0 || index > this->maximumTextSets || parameter < 0 || parameter >= DisplayTextSetParameterCount)
        return "";

    String value = "";
    this->getSetTextSetParameter(index, parameter, &value);

    return value;
}

LedDisplayController::GetSetTextSetParameterExitCode LedDisplayController::setTextSetParameter(int index, DisplayTextSetParameter parameter, String value)
{
    return this->getSetTextSetParameter(index, parameter, &value, true);
}

int LedDisplayController::getBrightness()
{
    return this->text_sets.disp_brightness;
}

bool LedDisplayController::setBrightness(int brightness)
{
    if (brightness < 0 || brightness > 255)
        return false;


    this->text_sets.disp_brightness = brightness;
    this->matrix->SetBrightness( adjustBrightnessToEnv() );
    this->matrix->Show();
    this->storeTextSets();
    return true;
}

bool LedDisplayController::getAutomaticBrightnessAdjustment()
{
    return this->text_sets.disp_automatic_brightness_adjustment;
}

void LedDisplayController::setAutomaticBrightnessAdjustment(bool automaticBrightnessAdjustment)
{
    this->text_sets.disp_automatic_brightness_adjustment = automaticBrightnessAdjustment;
    this->storeTextSets();
    brightness_init();
    measureBrightnessEnv();

}

// ------------------
// - Matrix control -
// ------------------

void LedDisplayController::brightness_init()
{
        for(uint8_t i = 0; i < meas_slots; i++)
            this->brightness_levels[i] = analogRead(LDR_PIN);
        this->current_meas_slot = 0;
        this->last_brightness_meas = millis();
        measureBrightnessEnv();
}

void LedDisplayController::disp_init()
{
    text_curr_nr = 0;
    text_set_starttime = 0;
    text_pass = 0;
    text_pos = this->matrix->width();
    disp_show = true;
    last_display_show_ms = 0;
    default_display_show_wait_ms = 200;
    display_show_wait_ms = 100;
    text_no_activ_legal_sets = true;
}

void LedDisplayController::disp_start_set()
{

    bool legal_entry_setting =
        (0 == text_set_starttime) ||
        // current entry is not scrolling:  maximum of runtime is defined and runtime is over
        ((text_sets.sets[text_curr_nr].scroll == false) && ((text_sets.sets[text_curr_nr].runtime > 0) && ((millis() - text_set_starttime) >= text_sets.sets[text_curr_nr].runtime))) ||
        // current entry is scrolling: but maximum number of scrolls is defined and reached
        ((text_sets.sets[text_curr_nr].scroll == true) && ((text_sets.sets[text_curr_nr].scrollCount > 0) && (text_pass >= text_sets.sets[text_curr_nr].scrollCount)));

    bool illegal_entry_settings =
        // current entry is not active
        (text_sets.sets[text_curr_nr].active == false) ||
        // current entry has no text defined
        (text_sets.sets[text_curr_nr].text[0] == '\0') ||
        // current entry is not scrolling:  but no runtime is defined
        ((text_sets.sets[text_curr_nr].scroll == false) && (text_sets.sets[text_curr_nr].runtime == 0)) ||
        // current entry is scrolling: but no scrollnumber defined
        ((text_sets.sets[text_curr_nr].scroll == true) && (text_sets.sets[text_curr_nr].scrollCount == 0));

    if (legal_entry_setting || illegal_entry_settings
        //(((text_sets.sets[text_curr_nr].scrollCount == 0) || text_sets.sets[text_curr_nr].scroll == false) && (text_sets.sets[text_curr_nr].runtime == 0) ) //&& ((millis() - text_set_starttime) >= 10000))
    )
    {

        //Serial.printf("[%lu] Meet start set condition. Curr set is %d. \n", millis(), text_curr_nr);
        if (0 < text_set_starttime) //|| text_sets.sets[text_curr_nr].text == '\0' || text_sets.sets[text_curr_nr].active == false)
            text_curr_nr++;
        if (text_curr_nr == maximumTextSets)
        {
            text_curr_nr = 0;
            if (false == text_no_activ_legal_sets)
            {
                this->matrix->fillScreen(0);
                this->matrix->Show();
            }

            text_no_activ_legal_sets = false;
        }
        text_pass = 0;
        if (text_sets.sets[text_curr_nr].active &&
            (((text_sets.sets[text_curr_nr].scroll == false) && (text_sets.sets[text_curr_nr].runtime > 0)) || ((text_sets.sets[text_curr_nr].scroll == true) && (text_sets.sets[text_curr_nr].scrollCount > 0))))
        {
            text_no_activ_legal_sets = true;
            //Serial.printf("[%lu] Set %d. Runtime %d. Text:'%s'\n", millis(), text_curr_nr, text_sets.sets[text_curr_nr].runtime, text_sets.sets[text_curr_nr].text);
            this->matrix->fillScreen(0);
            textpixel = 6 * strlen(text_sets.sets[text_curr_nr].text);
            // scrolling text has always left or right allignment - depending on direction of scroll
            if (true == text_sets.sets[text_curr_nr].scroll)
            {
                if(0 == text_sets.sets[text_curr_nr].scrollDirection)
                    text_pos = this->matrix->width();
                else
                    text_pos = 0 - textpixel;
            }
            else
            {          
                switch (text_sets.sets[text_curr_nr].alignment)
                {
                case AlignLeft:
                    text_pos = 0;
                    break;
                case AlignRight:
                    text_pos = this->matrix->width() - textpixel;
                    break;
                case AlignCenter:
                    text_pos = this->matrix->width() - textpixel;
                    text_pos = text_pos / 2;
                    break;
                }
            }

            show_matrix(text_sets.sets[text_curr_nr].text, text_pos, text_sets.sets[text_curr_nr].color);
            text_set_starttime = millis();
        }
    }
    else
    {
        //Serial.printf("[%lu] Don't meet start set condition. Text is: %s. Active is: %d\n", millis(), text_sets.sets[text_curr_nr].text, text_sets.sets[text_curr_nr].active);
    }
}

void LedDisplayController::disp_scroll_text()
{

    if (millis() - last_display_show_ms > display_show_wait_ms && text_sets.sets[text_curr_nr].scrollSpeed != 0)
    {

        last_display_show_ms = millis();
        display_show_wait_ms = ((2 * default_display_show_wait_ms) / text_sets.sets[text_curr_nr].scrollSpeed);
        //Serial.printf("speed %d, waittime: %d' \n", text_sets.sets[text_curr_nr].scrollSpeed, display_show_wait_ms);
        this->matrix->fillScreen(0);
        show_matrix(text_sets.sets[text_curr_nr].text, text_pos, text_sets.sets[text_curr_nr].color);
        if(0==text_sets.sets[text_curr_nr].scrollDirection)
        {
            (int)text_pos--;
            if (int(text_pos + textpixel) < 0)
            {
                text_pos = this->matrix->width();
                text_pass++;
                //Serial.printf("Pass[%d] - set nr %d, Text: '%s' \n", text_pass, text_curr_nr, text_sets.sets[text_curr_nr].text);
            }
        }
        else
        {
            (int)text_pos++;
            if (text_pos > this->matrix->width())
            {
                text_pos = 0 - textpixel;
                text_pass++;
                //Serial.printf("Pass[%d] - set nr %d, Text: '%s' \n", text_pass, text_curr_nr, text_sets.sets[text_curr_nr].text);
            }
        }
    }
}

void LedDisplayController::show_matrix(const char *text, int pos, const char *color)
{
    //Serial.printf("TEXT: %s (pos=%d, color=%d)\n", text, pos, this->colorFromHex(String(color)));
    this->matrix->SetBrightness( adjustBrightnessToEnv() );
    this->matrix->setTextColor(this->colorFromHex(String(color)));
    this->matrix->setCursor(pos, 0);
    this->matrix->print(text);
    //portDISABLE_INTERRUPTS();
    this->matrix->Show();
    //portENABLE_INTERRUPTS();
}


uint8_t LedDisplayController::adjustBrightnessToEnv( void )
{
    uint8_t brightness = ( pow (2, (this->text_sets.disp_brightness / RFade)) ) ;
    uint8_t brightness_adj = brightness * this->brightness_adjust  ;
    //Serial.printf("brightness without adjust : %d . Adjust is: %f. Brightness with adjust : %d .\n", brightness, this->brightness_adjust, brightness_adj);    
    if(brightness_adj < 1)
        brightness_adj = 1;
    return brightness_adj;
}

void LedDisplayController::measureBrightnessEnv( void )
{
    if(true == this->text_sets.disp_automatic_brightness_adjustment)
    {
        if( millis() - this->last_brightness_meas > 1000)
        {
            this->brightness_levels[this->current_meas_slot] = analogRead(LDR_PIN);  
            //Serial.printf("brightness last measured value = %d\n", brightness_levels[current_meas_slot]);
            this->current_meas_slot = this->current_meas_slot+1>=this->meas_slots? 0 : this->current_meas_slot+1;
            this->last_brightness_meas = millis();

            uint16_t smallest =  brightness_levels[0];
            uint16_t biggest = brightness_levels[0];
            uint64_t sum = brightness_levels[0];

            for(uint8_t i = 1; i < this->meas_slots; i++)
            {
                sum = sum + this->brightness_levels[i] ;
                if( this->brightness_levels[i] < smallest )
                    smallest = this->brightness_levels[i];
                else
                {
                    if( this->brightness_levels[i] > biggest )
                        biggest = this->brightness_levels[i];
                }
            }

            sum = sum - smallest - biggest;
            sum = sum / (this->meas_slots - 2);
            //Serial.printf("brightness median value = %llu \n", sum);

            float adjust = ( ( ( ( 100.0/ ( 4095.0 / 2.0 ) ) * (float)sum ) ) / 100.0 );
            adjust = adjust > 1.0 ? 1.0 : adjust ;
            adjust = adjust < 0.2 ? 0.1 : adjust ;
            this->brightness_adjust = adjust;
            Serial.printf("brightness_adjust = %f\n", this->brightness_adjust);
        }
    }
    else
    {
        this->brightness_adjust = 1.0;
    }
}


uint16_t LedDisplayController::colorFromHex(String hex)
{

    hex.replace("#", "");

    long number = (long)strtol(&hex[0], NULL, 16);
    int r = number >> 16;
    int g = number >> 8 & 0xFF;
    int b = number & 0xFF;

    //Serial.println("Color is: " + hex + " rgb are: r: " + r + " b: " + b + " g: " + g);

    return this->matrix->Color(r, g, b);
}

// -------------------
// - Storage control -
// -------------------

LedDisplayController::GetSetTextSetParameterExitCode LedDisplayController::getSetTextSetParameter(int index, DisplayTextSetParameter parameter, String *value, bool set)
{
    if (parameter < 0 || parameter >= DisplayTextSetParameterCount)
        return InvalidParameterError;
    else if (index < 0 || index >= this->maximumTextSets)
        return IndexOutOfRangeError;
    else if (value == nullptr)
        return ValueIsNullptrError;
    else if (strcmp(this->text_sets.valid, "OK") != 0)
        return InternalError;

    text_set_t *currentTextSet = &this->text_sets.sets[index];
    String returnValue = "";

    switch (parameter)
    {
    case TextParameter:
        if (set)
        {
            if (*value == "") {
                // delete the index

                Serial.println("Deleting: " + String(index));

                for (int i = index; i < this->maximumTextSets; i++)
                {
                    if (i < this->maximumTextSets - 1)
                        this->text_sets.sets[i] = this->text_sets.sets[i + 1];
                    else
                        this->text_sets.sets[i] = this->defaultTextSet;
                }
            }
            else
                strncpy(currentTextSet->text, value->c_str(), sizeof(currentTextSet->text));
        }
        else
            returnValue = String(currentTextSet->text);
        break;

    case ActiveParameter:
        if (set)
            currentTextSet->active = *value == "true";
        else
            returnValue = currentTextSet->active ? "true" : "false";
        break;

    case RuntimeParameter:
        if (set)
            currentTextSet->runtime = value->toInt() * 1000;
        else
            returnValue = String((int)round(currentTextSet->runtime / 1000));
        //Serial.printf("Runtime is: %d \n", currentTextSet->runtime);
        break;

    case ColorParameter:
        if (set)
            strncpy(currentTextSet->color, value->c_str(), sizeof(currentTextSet->color));
        else
            returnValue = String(currentTextSet->color);
        break;

    case AlignmentParameter:
        if (set)
        {
            if (value->toInt() < 0 || value->toInt() > AlignRight)
                return ValueOutOfRangeError;
            currentTextSet->alignment = text_align_t(value->toInt());
        }
        else
            returnValue = String(currentTextSet->alignment);
        break;

    case ScrollParameter:
        if (set)
            currentTextSet->scroll = *value == "true";
        else
            returnValue = currentTextSet->scroll ? "true" : "false";
        break;

    case ScrollDirectionParameter:
        if (set)
            currentTextSet->scrollDirection = value->toInt();
        else
            returnValue = currentTextSet->scrollDirection;
        break;
    case ScrollSpeedParameter:
        if (set)
        {
            if (value->toInt() < 0 || value->toInt() > 10)
                return ValueOutOfRangeError;
            currentTextSet->scrollSpeed = value->toInt();
        }
        else
            returnValue = String(currentTextSet->scrollSpeed);
        break;

    case ScrollCountParameter:
        if (set)
        {
            if (value->toInt() < 0)
                return ValueOutOfRangeError;
            currentTextSet->scrollCount = value->toInt();
        }
        else
            returnValue = String(currentTextSet->scrollCount);
        break;

    case IndexParameter:
        if (set)
            return ParameterNotWritableError;
        else
            returnValue = String(index);
        break;
    default:
        break;
    }

    if (set)
    {
        this->storeTextSets();
        return Success;
    }
    else
    {
        *value = returnValue;
        return Success;
    }
}

bool LedDisplayController::storeTextSets()
{
    Serial.println("Text Sets where modified ...");
    this->setsModiefiedLastTime_ms = millis();
    this->setModifyingActive = true;    
    return true;
}

bool LedDisplayController::loadTextSets()
{
    sets_t buf = {};

    Serial.println("Loading Text Sets...");

    if (this->eepromUnit != nullptr)
    {
        // read conf from EEPROM
        this->eepromUnit->read(buf);
    }

    Serial.println("Loaded text sets: valid: " + String(buf.valid));

    if (strcmp(buf.valid, "OK") == 0)
    {
        memcpy(&text_sets, &buf, sizeof(sets_t));
        //Serial.printf("OK: '%s\n", text_sets.sets[0].text);
        return true;
    }
    else
    {
        // There was an error reading the sets -> rebuild with default values!

        sets_t defaultTextSets;

        for (int i = 0; i < this->maximumTextSets; i++)
        {
            defaultTextSets.sets[i] = this->defaultTextSet;
        }

        memcpy(&text_sets, &defaultTextSets, sizeof(sets_t));
        text_sets.disp_brightness = 70;
        text_sets.disp_automatic_brightness_adjustment = false;
        return this->storeTextSets();
    }
}

// ----------
// - Extern -
// ----------

void updateDisplayGlobal(void *object)
{
    for (;;)
    {
        esp_task_wdt_reset();
        delay(1);
        ((LedDisplayController *)(object))->loop();
    }
}