/*
PocketScreen.cpp
Last modified 07 Oct 2020

This file is part of the PocketScreen Library.

Copyright (C) 2018-2020  Florian Keller

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 <http://www.gnu.org/licenses/>.
*/

#include "PocketScreen.h"

#define PS_send(x) SERCOM4->SPI.DATA.bit.DATA = (x);
#define PS_wait() while (SERCOM4->SPI.INTFLAG.bit.DRE == 0);

// init
PocketScreen::PocketScreen() {
    cursorX = 0;
    cursorY = 0;
    fontHeight = 0;
    fontFirstChar = 0;
    fontLastChar = 0;
    fontDescriptor = 0;
    fontBitmap = 0;
    fontColor = 0xFFFF;
    fontColorBackground = 0x0000;
    bitDepth16 = false;
    mirror = false;
    colorModeBGR = false;
    PocketScreen_SPI = &SPI1;
}

void PocketScreen::begin(void) {
    PocketScreen_SPI->begin();
    PocketScreen_SPI->setDataMode(SPI_MODE0);
    PocketScreen_SPI->setClockDivider(4);

    pinMode(PIN_SHDN, OUTPUT);
    pinMode(PIN_DC, OUTPUT);
    pinMode(PIN_CS, OUTPUT);
    pinMode(PIN_RST, OUTPUT);
    
    pinMode(PIN_LED_RED,   OUTPUT);
    pinMode(PIN_LED_GREEN, OUTPUT);
    pinMode(PIN_LED_BLUE,  OUTPUT);
    digitalWrite(PIN_LED_RED, HIGH);
    digitalWrite(PIN_LED_GREEN, HIGH);
    digitalWrite(PIN_LED_BLUE, HIGH);
    
    pinMode(PIN_BUTTON_0, INPUT_PULLUP);
    pinMode(PIN_BUTTON_1, INPUT_PULLUP);
    pinMode(PIN_BUTTON_2, INPUT_PULLUP);
    
    pinMode(PIN_BATTERY_LEVEL, INPUT);
    pinMode(PIN_BATTERY_CHARGING, INPUT);
    
    pinMode(PIN_SD_CHECK, INPUT_PULLUP);
    
    digitalWrite(PIN_SHDN, LOW);
    digitalWrite(PIN_DC, HIGH);
    digitalWrite(PIN_CS, HIGH);
    digitalWrite(PIN_RST, HIGH);
    
    digitalWrite(PIN_RST, LOW);
    delay(5);
    digitalWrite(PIN_RST, HIGH);
    delay(10);
    
    const uint8_t init[32] = {0xAE, 0xA1, 0x00, 0xA2, 0x00, 0xA4, 0xA8, 0x3F, 0xAD, 0x8E, 0xB0, 0x0B, 0xB1, 0x31, 0xB3, 0xF0, 0x8A, 0x64, 0x8B, 0x78, 0x8C, 0x64, 0xBB, 0x3A, 0xBE, 0x3E, 0x81, 0x91, 0x82, 0x50, 0x83, 0x7D};
    off();
    startCommandTransfer();
    for (uint8_t i = 0; i < 32; i++)
        PocketScreen_SPI->transfer(init[i]);
    endTransfer();
    
    writeRemap();
    setBrightness(5);
    clearScreen();
    on();
}

// general control
void PocketScreen::startTransfer(void) {
    digitalWrite(PIN_DC, HIGH);
    digitalWrite(PIN_CS, LOW);
}

void PocketScreen::startCommandTransfer(void) {
    digitalWrite(PIN_DC, LOW);
    digitalWrite(PIN_CS, LOW);
}

void PocketScreen::endTransfer(void) {
    digitalWrite(PIN_CS, HIGH);
}

void PocketScreen::on(void) {
    digitalWrite(PIN_SHDN, HIGH);
    startCommandTransfer();
    delayMicroseconds(10000);
    PocketScreen_SPI->transfer(COMMAND_DISPLAY_ON);
    endTransfer();
}

void PocketScreen::off(void) {
    startCommandTransfer();
    PocketScreen_SPI->transfer(COMMAND_DISPLAY_OFF);
    endTransfer();
    digitalWrite(PIN_SHDN, LOW);
}

void PocketScreen::setMirror(boolean _mirror) {
    mirror = _mirror;
    writeRemap();
}

void PocketScreen::setBitDepth(uint8_t bitDepth) {
    bitDepth16 = bitDepth;
    writeRemap();
}

void PocketScreen::setColorMode(uint8_t colorMode) {
    colorModeBGR = colorMode;
    writeRemap();
}

void PocketScreen::writeRemap() {
    uint8_t remap = (1 << 5);
    if (mirror)
        remap ^= (1 << 1);
    if (bitDepth16)
        remap ^= (1 << 6);
    if (colorModeBGR)
        remap ^= (1 << 2);
    
    remap ^= ((1 << 4) | (1 << 1));
    
    startCommandTransfer();
    PocketScreen_SPI->transfer(COMMAND_SET_REMAP);
    PocketScreen_SPI->transfer(remap);
    endTransfer();
}

void PocketScreen::setBrightness(uint8_t brightness) {
    if (brightness > 15)
        brightness = 15;    
    startCommandTransfer();
    PocketScreen_SPI->transfer(COMMAND_MASTER_CURRENT);
    PocketScreen_SPI->transfer(brightness);
    endTransfer();
}

// accelerated graphics commands
void PocketScreen::clearWindow(uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
    if (x > xMax || y > yMax)
        return;
    uint8_t x2 = x + width - 1;
    uint8_t y2 = y + height - 1;
    if (x2 > xMax)
        x2 = xMax;
    if (y2 > yMax)
        y2 = yMax;
    
    startCommandTransfer();
    PocketScreen_SPI->transfer(COMMAND_CLEAR_WINDOW);
    PocketScreen_SPI->transfer(x);
    PocketScreen_SPI->transfer(y);
    PocketScreen_SPI->transfer(x2);
    PocketScreen_SPI->transfer(y2);
    endTransfer();
    delayMicroseconds(400);
}

void PocketScreen::clearScreen() {
    clearWindow(0, 0, 96, 64);
}

void PocketScreen::drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color) {
    uint8_t red = (color >> 5) & 0x7;
    uint8_t green = (color >> 2) & 0x7;
    uint8_t blue = color & 0x3;
    
    red = red | (red << 3);
    green = green | (green << 3);
    blue = blue | (blue << 2) | (blue << 4);
    
    drawLine(x1, y1, x2, y2, red, green, blue);
}

void PocketScreen::drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint16_t color) {
    uint8_t red = (color >> 10) & 0x3E;
    uint8_t green = (color >> 5) & 0x3F;
    uint8_t blue = (color << 1) & 0x3E;
    
    drawLine(x1, y1, x2, y2, red, green, blue);
}

void PocketScreen::drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t red, uint8_t green, uint8_t blue) {
    if (x1 > xMax)
        x1 = xMax;
    if (y1 > yMax)
        y1 = yMax;
    if (x2 > xMax)
        x2 = xMax;
    if (y2 > yMax)
        y2 = yMax;
    
    startCommandTransfer();
    PocketScreen_SPI->transfer(COMMAND_DRAW_LINE);
    PocketScreen_SPI->transfer(x1);
    PocketScreen_SPI->transfer(y1);
    PocketScreen_SPI->transfer(x2);
    PocketScreen_SPI->transfer(y2);
    PocketScreen_SPI->transfer(red);
    PocketScreen_SPI->transfer(green);
    PocketScreen_SPI->transfer(blue);
    endTransfer();
    delayMicroseconds(100);
}

void PocketScreen::drawRect(uint8_t x, uint8_t y, uint8_t width, uint8_t height, boolean fill, uint8_t color) {
    uint8_t red = (color >> 5) & 0x7;
    uint8_t green = (color >> 2) & 0x7;
    uint8_t blue = color & 0x3;
    
    red = red | (red << 3);
    green = green | (green << 3);
    blue = blue | (blue << 2) | (blue << 4);
    
    drawRect(x, y, width, height, fill, red, green, blue);
}

void PocketScreen::drawRect(uint8_t x, uint8_t y, uint8_t width, uint8_t height, boolean fill, uint16_t color) {
    uint8_t red = (color >> 10) & 0x3E;
    uint8_t green = (color >> 5) & 0x3F;
    uint8_t blue = (color << 1) & 0x3E;
    
    drawRect(x, y, width, height, fill, red, green, blue);
}

void PocketScreen::drawRect(uint8_t x, uint8_t y, uint8_t width, uint8_t height, boolean fill, uint8_t red, uint8_t green, uint8_t blue) {
    if (x > xMax || y > yMax || width == 0 || height == 0)
        return;
    uint8_t x2 = x + width - 1;
    uint8_t y2 = y + height - 1;
    if (x2 > xMax)
        x2 = xMax;
    if (y2 > yMax)
        y2 = yMax;
    
    uint8_t fillx = 0;
    if (fill)
        fillx = 1;
    
    startCommandTransfer();
    PocketScreen_SPI->transfer(COMMAND_SET_FILL);
    PocketScreen_SPI->transfer(fillx);
    PocketScreen_SPI->transfer(COMMAND_DRAW_RECTANGLE);
    PocketScreen_SPI->transfer(x);
    PocketScreen_SPI->transfer(y);
    PocketScreen_SPI->transfer(x2);
    PocketScreen_SPI->transfer(y2);
    PocketScreen_SPI->transfer(red);
    PocketScreen_SPI->transfer(green);
    PocketScreen_SPI->transfer(blue);
    PocketScreen_SPI->transfer(red);
    PocketScreen_SPI->transfer(green);
    PocketScreen_SPI->transfer(blue);
    endTransfer();
    delayMicroseconds(400);
}

// pixel manipulation
void PocketScreen::drawPixel(uint8_t x, uint8_t y, uint16_t color) {
    if (x > xMax || y > yMax)
        return;
    goTo(x, y);
    startTransfer();
    if (bitDepth16)
        PocketScreen_SPI->transfer(color >> 8);
    PocketScreen_SPI->transfer(color);
    endTransfer();
}

void PocketScreen::setX(uint8_t start, uint8_t end) {
    if(start > xMax)
        start = xMax;
    if(end > xMax)
        end = xMax;
    startCommandTransfer();
    PocketScreen_SPI->transfer(COMMAND_SET_COLOUMN);
    PocketScreen_SPI->transfer(start);
    PocketScreen_SPI->transfer(end);
    endTransfer();
}

void PocketScreen::setY(uint8_t start, uint8_t end) {
    if(start > yMax)
        start = yMax;
    if(end > yMax)
        end = yMax;
    startCommandTransfer();
    PocketScreen_SPI->transfer(COMMAND_SET_ROW);
    PocketScreen_SPI->transfer(start);
    PocketScreen_SPI->transfer(end);
    endTransfer();
}

void PocketScreen::goTo(uint8_t x, uint8_t y) {
    if(x > xMax || y > yMax)
        return;
    setX(x, xMax);
    setY(y, yMax);
}

void PocketScreen::writeBuffer(const uint8_t *buffer, int count) {
    uint8_t temp;
    PS_send(buffer[0]);
    for (int i = 1; i < count; i++) {
        temp = buffer[i];
        PS_wait();
        PS_send(temp);
    }
    PS_wait();
}

void PocketScreen::writeBuffer(const uint16_t *buffer, int count) {
    uint16_t temp;
    PS_send(buffer[0] >> 8);
    PS_wait();
    PS_send(buffer[0]);
    for (int i = 1; i < count; i++) {
        temp = buffer[i];
        PS_wait();
        PS_send(temp >> 8);
        PS_wait();
        PS_send(temp);
    }
    PS_wait();
}

// printing text
void PocketScreen::setFont(const PSFont &font) {
    fontHeight = font.height;
    fontFirstChar = font.startChar;
    fontLastChar = font.endChar;
    fontDescriptor = font.charInfo;
    fontBitmap = font.bitmap;
}

void PocketScreen::setFontColor(uint16_t color, uint16_t colorBackground) {
    fontColor = color;
    fontColorBackground = colorBackground;
}

void PocketScreen::setCursor(uint8_t x, uint8_t y) {
    cursorX = x;
    cursorY = y;
}

uint8_t PocketScreen::getPrintWidth(char *text) {
    if(!fontFirstChar)
        return 0;
    uint8_t charAmount, result = 0;
    charAmount = strlen(text);
    for(int i = 0; i < charAmount; i++) {
        result += pgm_read_byte(&fontDescriptor[text[i] - fontFirstChar].width) + 1;
    }
    return result;
}

size_t PocketScreen::write(uint8_t c) {
    if (!fontFirstChar)
        return 1;
    if (c < fontFirstChar || c > fontLastChar)
        return 1;
    if (cursorX > xMax || cursorY > yMax)
        return 1;
    
    uint8_t charWidth = pgm_read_byte(&fontDescriptor[c - fontFirstChar].width);
    uint8_t bytesPerRow = charWidth / 8;
    if (charWidth > bytesPerRow * 8)
        bytesPerRow++;
    uint16_t offset = pgm_read_word(&fontDescriptor[c - fontFirstChar].offset) + (bytesPerRow * fontHeight) - 1;
    
    setX(cursorX, cursorX + charWidth + 1);
    setY(cursorY, cursorY + fontHeight);
    
    startTransfer();
    for (uint8_t y = 0; y < fontHeight && y + cursorY < yMax + 1; y++) {
        if (bitDepth16) {
            PS_send(fontColorBackground >> 8);
            PS_wait();
        }
        PS_send(fontColorBackground);
        for (uint8_t byte = 0; byte < bytesPerRow; byte++) {
            uint8_t data = pgm_read_byte(fontBitmap + offset - y - ((bytesPerRow - byte - 1) * fontHeight));
            uint8_t bits = byte * 8;
            for (uint8_t i = 0; i < 8 && (bits + i) < charWidth && (bits + i + cursorX) < xMax; i++) {
                PS_wait();
                if (data & (0x80 >> i)) {
                    if (bitDepth16) {
                        PS_send(fontColor >> 8);
                        PS_wait();
                    }
                    PS_send(fontColor);
                 } else {
                    if (bitDepth16) {
                        PS_send(fontColorBackground >> 8);
                        PS_wait();
                    }
                    PS_send(fontColorBackground);
                }
            }
        }
        PS_wait();
        if ((cursorX + charWidth) < xMax) {
            if (bitDepth16) {
                PS_send(fontColorBackground >> 8);
                PS_wait();
            }
            PS_send(fontColorBackground);
            PS_wait();
        }
    }
    endTransfer();
    cursorX += (charWidth + 1);
    return 1;
}

boolean PocketScreen::isSDInserted() {
    return !digitalRead(PIN_SD_CHECK);
}

void PocketScreen::setLEDColor(uint8_t color) {
    switch (color) {
        case LEDOff:
        default:
            digitalWrite(PIN_LED_RED,   HIGH);
            digitalWrite(PIN_LED_GREEN, HIGH);
            digitalWrite(PIN_LED_BLUE,  HIGH);
            break;
        case LEDRed:
            digitalWrite(PIN_LED_RED,   LOW);
            digitalWrite(PIN_LED_GREEN, HIGH);
            digitalWrite(PIN_LED_BLUE,  HIGH);
            break;
        case LEDGreen:
            digitalWrite(PIN_LED_RED,   HIGH);
            digitalWrite(PIN_LED_GREEN, LOW);
            digitalWrite(PIN_LED_BLUE,  HIGH);
            break;
        case LEDBlue:
            digitalWrite(PIN_LED_RED,   HIGH);
            digitalWrite(PIN_LED_GREEN, HIGH);
            digitalWrite(PIN_LED_BLUE,  LOW);
            break;
    }
}

// battery information
int PocketScreen::batteryLevel() {
    return analogRead(PIN_BATTERY_LEVEL);
}

boolean PocketScreen::isCharging() {
    return !digitalRead(PIN_BATTERY_CHARGING);
}