/*
 * Copyright (C) 2019  Max Regan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <cstring>

#include "Application/Screens/SetDateScreen.h"
#include "Application/SystemFonts.h"
#include "Bsp/SystemTime.h"
#include "Bsp/Drivers/RtcDriver.h"

using BSP::ReturnCode;
using BSP::Time;
using BSP::Schedule::NextTime;

SetDateScreen::SetDateScreen(BSP::DisplayDriver &display,
                             ScreenManager &manager)
    : m_display(display)
    , m_manager(manager)
    , m_state(SetState::HOURS)
    , m_is_acked(false)
    , m_time()
    , m_font(default_font)
    , m_row_spacing((m_display.get_height() - m_font.height * 2) / 3)
    , m_row_0_y(m_row_spacing)
    , m_row_1_y(m_row_0_y + m_font.height + m_row_spacing + m_font.height)
{}

ReturnCode SetDateScreen::init()
{
    return ReturnCode::OK;
}

NextTime SetDateScreen::execute()
{
    //TODO: Fix this so it doesn't constantly refresh
    refresh();

    return NextTime::never();
}

static char get_char_for_digit(uint8_t bcd_digit)
{
    if (bcd_digit > 9) {
        return '0';
    }
    return bcd_digit + '0';
}

void SetDateScreen::display_number(uint32_t *x, uint32_t y,
                                   uint32_t tens, uint32_t ones)
{
    char time_str[3] = { 0 };
    time_str[0] = get_char_for_digit(tens);
    time_str[1] = get_char_for_digit(ones);
    time_str[2] = '\0';


    m_display.string_at(x, y, time_str, &m_font);
}

const char *SetDateScreen::get_acknak_string()
{
    if (!m_is_acked) {
        return "Cancel";
    } else {
        return "OK";
    }
}

uint32_t SetDateScreen::get_acknak_string_len()
{
    std::size_t len = strlen(get_acknak_string());
    return len * m_font.width - 1;
}

uint32_t SetDateScreen::get_acknak_string_x_pos()
{
    std::size_t len = strlen(get_acknak_string());
    return m_display.get_width() - len * m_font.width - 1;
}

void SetDateScreen::render_time()
{
    uint32_t x = 0;
    uint32_t y = m_row_spacing;

    display_number(&x, y,
                   m_time.get_hours_12_tens(), m_time.get_hours_12_ones());

    m_display.string_at(&x, y, ":", &m_font);

    display_number(&x, y,
                   m_time.get_minutes_tens(), m_time.get_minutes_ones());

    m_display.string_at(&x, y, ":", &m_font);

    display_number(&x, y,
                   m_time.get_seconds_tens(), m_time.get_seconds_ones());

    y = m_row_1_y;
    x = 0;
    m_display.string_at(&x, y, m_time.get_is_pm() ? "PM" : "AM", &m_font);

    x = get_acknak_string_x_pos();
    m_display.string_at(&x, y, get_acknak_string(), &m_font);

    m_display.refresh();
}

void SetDateScreen::draw_line(uint32_t x, uint32_t y, uint32_t width)
{
    m_display.draw_hline(x, y, width);
    m_display.draw_hline(x, y + 1, width);
}

void SetDateScreen::render_selection()
{
    switch (m_state) {
    case SetState::HOURS:
        draw_line(0, m_row_0_y + m_font.height, m_font.width * 2);
        break;
    case SetState::MINUTES:
        draw_line(m_font.width * 3, m_row_0_y + m_font.height, m_font.width * 2);
        break;
    case SetState::SECONDS:
        draw_line(m_font.width * 6, m_row_0_y + m_font.height, m_font.width * 2);
        break;
    case SetState::AM_PM:
        draw_line(0, m_row_1_y + m_font.height, m_font.width * 2);
        break;
    case SetState::ACK_NACK:
        draw_line(get_acknak_string_x_pos(), m_row_1_y + m_font.height, get_acknak_string_len());
        break;
    }
}

void SetDateScreen::refresh()
{
    m_display.clear();

    render_time();
    render_selection();

    m_display.refresh();
}

void SetDateScreen::enable()
{
    BSP::RtcDriver::get_time(m_time);
    m_state = SetState::HOURS;
    m_is_acked = true;

    refresh();
}

void SetDateScreen::disable()
{
    m_display.clear();
}

void SetDateScreen::notify_up_button()
{

    switch (m_state) {
    case SetState::HOURS:
        m_time.increment_hours();
        break;
    case SetState::MINUTES:
        m_time.increment_minutes();
        break;
    case SetState::SECONDS:
        m_time.increment_seconds();
        break;
    case SetState::AM_PM:
        m_time.toggle_am_pm();
        break;
    case SetState::ACK_NACK:
        m_is_acked = !m_is_acked;
        break;
    }

    refresh();
}

void SetDateScreen::notify_middle_button()
{
    switch (m_state) {
    case SetState::HOURS:
        m_state = SetState::MINUTES;
        break;
    case SetState::MINUTES:
        m_state = SetState::SECONDS;
        break;
    case SetState::SECONDS:
        m_state = SetState::AM_PM;
        break;
    case SetState::AM_PM:
        m_state = SetState::ACK_NACK;
        break;
    case SetState::ACK_NACK:
        if (m_is_acked) {
            BSP::RtcDriver::set_time(m_time);
        }
        m_manager.pop_screen();
        return;
    }

    refresh();
}

void SetDateScreen::notify_down_button()
{
    switch(m_state) {
    case SetState::HOURS:
        m_time.decrement_hours();
        break;
    case SetState::MINUTES:
        m_time.decrement_minutes();
        break;
    case SetState::SECONDS:
        m_time.decrement_seconds();
        break;
    case SetState::AM_PM:
        m_time.toggle_am_pm();
        break;
    case SetState::ACK_NACK:
        m_is_acked = !m_is_acked;
        break;
    }

    refresh();
}
