/******************************************************************************!
 * \file Output.cpp
 * \author Sebastien Beaugrand
 * \sa http://beaugrand.chez.com/
 * \copyright CeCILL 2.1 Free Software license
 ******************************************************************************/
#include <filesystem>
#include <fstream>
#include "Output.h"
#include "log.h"

#define DEVICE_ADDRESS 0x3C

/******************************************************************************!
 * \fn Output
 ******************************************************************************/
Output::Output()
{
    this->open();
    mScreensaverThread = std::thread(Output::run, this);
}

/******************************************************************************!
 * \fn ~Output
 ******************************************************************************/
Output::~Output()
{
    DEBUG("");
    this->loop = false;
    mScreensaverThread.join();
    this->close();
}

/******************************************************************************!
 * \fn open
 ******************************************************************************/
void
Output::open()
{
    if (mOled != nullptr) {
        return;
    }

#   if defined(__arm__) || defined(__aarch64__)
    try {
        mOled = new upm::SSD1306(0, DEVICE_ADDRESS);
    } catch (...) {
        mOled = new upm::SSD1306(1, DEVICE_ADDRESS);
    }
#   else
    mOled = new Terminal;
#   endif
    if (mOled == nullptr) {
        return;
    }
    mOled->clear();
#   if defined(__arm__) || defined(__aarch64__)
    // SH1106 workarround
    for (unsigned int i = 0; i < LCD_ROWS; ++i) {
        mOled->setCursor(i, LCD_COLS - 2);
        mOled->write("  ");
    }
    mOled->dim(true);
#   endif
}

/******************************************************************************!
 * \fn write
 ******************************************************************************/
void
Output::write(std::string_view line1,
              std::string_view line2,
              std::string_view line3,
              std::string_view line4)
{
    if (mOled == nullptr) {
        return;
    } else {
        const std::lock_guard<std::mutex> lock(mMutex);
        this->save = false;
        mOled->clear();

        std::string buff;
        buff = std::string(line1).substr(0, LCD_COLS);
        mOled->setCursor(1, 0);
        mOled->write(buff);
        buff = std::string(line2).substr(0, LCD_COLS);
        mOled->setCursor(3, 0);
        mOled->write(buff);
        buff = std::string(line3).substr(0, LCD_COLS);
        mOled->setCursor(5, 0);
        mOled->write(buff);
        buff = std::string(line4).substr(0, LCD_COLS);
        mOled->setCursor(7, 0);
        mOled->write(buff);
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

/******************************************************************************!
 * \fn close
 ******************************************************************************/
void
Output::close()
{
    if (mOled == nullptr) {
        return;
    }
    mOled->clear();
    delete mOled;
    mOled = nullptr;
}

/******************************************************************************!
 * \fn screensaver
 ******************************************************************************/
void
Output::screensaver()
{
    const unsigned int width = 5;
    const unsigned int height = 5;
    static int r = -1;

    const std::lock_guard<std::mutex> lock(mMutex);
    mOled->clear();

    std::time_t time = std::time({});
    char timeString[std::size("dd-mm hh:mm")];
    if (std::filesystem::exists(this->musicDirectory)) {
        std::strftime(std::data(timeString), std::size(timeString),
                      "%d-%m %H:%M", std::localtime(&time));
    } else {
        std::strftime(std::data(timeString), std::size(timeString),
                      "%dX%m %H:%M", std::localtime(&time));
    }

    if (r == -1) {
        srand(time);
    }
    r = rand() % ((LCD_COLS - (width - 1)) * (LCD_ROWS - (height - 1)));
    int x = r % (LCD_COLS - (width - 1));
    int y = r / (LCD_COLS - (width - 1));
    if (mOled->setCursor(y, x) != mraa::SUCCESS) {
        return;
    }

    timeString[width] = '\0';
    mOled->write(timeString);
    mOled->setCursor(y + height - 1, x);
    mOled->write(timeString + width + 1);

    if (std::filesystem::exists("/run/shutter.at")) {
        std::ifstream file("/run/shutter.at");
        std::string line;
        std::getline(file, line);
        if (! line.empty()) {
            mOled->setCursor(y + 2, x);
            mOled->write(line);
        }
    }
}

/******************************************************************************!
 * \fn run
 ******************************************************************************/
void
Output::run(Output* self)
{
    while (self->loop) {
        self->save = true;
        for (int i = 0; i < 15; ++i) {
            std::this_thread::sleep_for(std::chrono::seconds(2));
            if (! self->loop) {
                return;
            }
            if (! self->save) {
                break;
            }
        }
        if (! self->save) {
            continue;
        }

        if (self->mOled == nullptr) {
            return;
        } else {
            self->screensaver();
        }
    }
}