//
// iceZ0mb1e - FPGA 8-Bit TV80 SoC for Lattice iCE40
// with complete open-source toolchain flow using yosys and SDCC
//
// Copyright (c) 2018 Franz Neumann (netinside2000@gmx.de)
//
// 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 <stdint.h>
#include "mini-string.h"
#include "i2c.h"
#include "ssd1306.h"

const uint8_t fontData[][SSD1306_FONT_WIDTH] = {
    {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x00,0x07,0x00,0x07,0x00,0x00,0x00},
    {0x00,0x14,0x7F,0x14,0x7F,0x14,0x00,0x00},
    {0x00,0x24,0x2A,0x7F,0x2A,0x12,0x00,0x00},
    {0x00,0x23,0x13,0x08,0x64,0x62,0x00,0x00},
    {0x00,0x36,0x49,0x55,0x22,0x50,0x00,0x00},
    {0x00,0x00,0x05,0x03,0x00,0x00,0x00,0x00},
    {0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00},
    {0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00},
    {0x00,0x08,0x2A,0x1C,0x2A,0x08,0x00,0x00},
    {0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00},
    {0x00,0xA0,0x60,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00},
    {0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x20,0x10,0x08,0x04,0x02,0x00,0x00},
    {0x00,0x3E,0x51,0x49,0x45,0x3E,0x00,0x00}, //0
    {0x00,0x00,0x42,0x7F,0x40,0x00,0x00,0x00}, //1
    {0x00,0x62,0x51,0x49,0x49,0x46,0x00,0x00}, //2
    {0x00,0x22,0x41,0x49,0x49,0x36,0x00,0x00}, //3
    {0x00,0x18,0x14,0x12,0x7F,0x10,0x00,0x00}, //4
    {0x00,0x27,0x45,0x45,0x45,0x39,0x00,0x00}, //5
    {0x00,0x3C,0x4A,0x49,0x49,0x30,0x00,0x00}, //6
    {0x00,0x01,0x71,0x09,0x05,0x03,0x00,0x00}, //7
    {0x00,0x36,0x49,0x49,0x49,0x36,0x00,0x00}, //8
    {0x00,0x06,0x49,0x49,0x29,0x1E,0x00,0x00}, //9
    {0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00}, //A
    {0x00,0x00,0xAC,0x6C,0x00,0x00,0x00,0x00},
    {0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00},
    {0x00,0x14,0x14,0x14,0x14,0x14,0x00,0x00},
    {0x00,0x41,0x22,0x14,0x08,0x00,0x00,0x00},
    {0x00,0x02,0x01,0x51,0x09,0x06,0x00,0x00},
    {0x00,0x32,0x49,0x79,0x41,0x3E,0x00,0x00},
    {0x00,0x7E,0x09,0x09,0x09,0x7E,0x00,0x00},
    {0x00,0x7F,0x49,0x49,0x49,0x36,0x00,0x00},
    {0x00,0x3E,0x41,0x41,0x41,0x22,0x00,0x00},
    {0x00,0x7F,0x41,0x41,0x22,0x1C,0x00,0x00},
    {0x00,0x7F,0x49,0x49,0x49,0x41,0x00,0x00},
    {0x00,0x7F,0x09,0x09,0x09,0x01,0x00,0x00},
    {0x00,0x3E,0x41,0x41,0x51,0x72,0x00,0x00},
    {0x00,0x7F,0x08,0x08,0x08,0x7F,0x00,0x00},
    {0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00},
    {0x00,0x20,0x40,0x41,0x3F,0x01,0x00,0x00},
    {0x00,0x7F,0x08,0x14,0x22,0x41,0x00,0x00},
    {0x00,0x7F,0x40,0x40,0x40,0x40,0x00,0x00},
    {0x00,0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00},
    {0x00,0x7F,0x04,0x08,0x10,0x7F,0x00,0x00},
    {0x00,0x3E,0x41,0x41,0x41,0x3E,0x00,0x00},
    {0x00,0x7F,0x09,0x09,0x09,0x06,0x00,0x00},
    {0x00,0x3E,0x41,0x51,0x21,0x5E,0x00,0x00},
    {0x00,0x7F,0x09,0x19,0x29,0x46,0x00,0x00},
    {0x00,0x26,0x49,0x49,0x49,0x32,0x00,0x00}, // Z
    {0x00,0x01,0x01,0x7F,0x01,0x01,0x00,0x00},
    {0x00,0x3F,0x40,0x40,0x40,0x3F,0x00,0x00},
    {0x00,0x1F,0x20,0x40,0x20,0x1F,0x00,0x00},
    {0x00,0x3F,0x40,0x38,0x40,0x3F,0x00,0x00},
    {0x00,0x63,0x14,0x08,0x14,0x63,0x00,0x00},
    {0x00,0x03,0x04,0x78,0x04,0x03,0x00,0x00},
    {0x00,0x61,0x51,0x49,0x45,0x43,0x00,0x00},
    {0x00,0x7F,0x41,0x41,0x00,0x00,0x00,0x00},
    {0x00,0x02,0x04,0x08,0x10,0x20,0x00,0x00},
    {0x00,0x41,0x41,0x7F,0x00,0x00,0x00,0x00},
    {0x00,0x04,0x02,0x01,0x02,0x04,0x00,0x00},
    {0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00},
    {0x00,0x01,0x02,0x04,0x00,0x00,0x00,0x00},
    {0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00},
    {0x00,0x7F,0x48,0x44,0x44,0x38,0x00,0x00},
    {0x00,0x38,0x44,0x44,0x28,0x00,0x00,0x00},
    {0x00,0x38,0x44,0x44,0x48,0x7F,0x00,0x00},
    {0x00,0x38,0x54,0x54,0x54,0x18,0x00,0x00},
    {0x00,0x08,0x7E,0x09,0x02,0x00,0x00,0x00},
    {0x00,0x18,0xA4,0xA4,0xA4,0x7C,0x00,0x00},
    {0x00,0x7F,0x08,0x04,0x04,0x78,0x00,0x00},
    {0x00,0x00,0x7D,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x80,0x84,0x7D,0x00,0x00,0x00,0x00},
    {0x00,0x7F,0x10,0x28,0x44,0x00,0x00,0x00},
    {0x00,0x41,0x7F,0x40,0x00,0x00,0x00,0x00},
    {0x00,0x7C,0x04,0x18,0x04,0x78,0x00,0x00},
    {0x00,0x7C,0x08,0x04,0x7C,0x00,0x00,0x00},
    {0x00,0x38,0x44,0x44,0x38,0x00,0x00,0x00},
    {0x00,0xFC,0x24,0x24,0x18,0x00,0x00,0x00},
    {0x00,0x18,0x24,0x24,0xFC,0x00,0x00,0x00},
    {0x00,0x00,0x7C,0x08,0x04,0x00,0x00,0x00},
    {0x00,0x48,0x54,0x54,0x24,0x00,0x00,0x00},
    {0x00,0x04,0x7F,0x44,0x00,0x00,0x00,0x00},
    {0x00,0x3C,0x40,0x40,0x7C,0x00,0x00,0x00},
    {0x00,0x1C,0x20,0x40,0x20,0x1C,0x00,0x00},
    {0x00,0x3C,0x40,0x30,0x40,0x3C,0x00,0x00},
    {0x00,0x44,0x28,0x10,0x28,0x44,0x00,0x00},
    {0x00,0x1C,0xA0,0xA0,0x7C,0x00,0x00,0x00},
    {0x00,0x44,0x64,0x54,0x4C,0x44,0x00,0x00},
    {0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x00},
    {0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x41,0x36,0x08,0x00,0x00,0x00,0x00},
    {0x00,0x02,0x01,0x01,0x02,0x01,0x00,0x00},
    {0x00,0x02,0x05,0x05,0x02,0x00,0x00,0x00}
};

uint8_t ssd1306_i2c_addr;

#define SSD1306_FBSIZE (SSD1306_PAGES * SSD1306_COLUMNS)
#define SSD1306_FBADDR(x,y) (y/SSD1306_PAGES * SSD1306_COLUMNS + x)
//Comment out SSD1306_FBPIXSET and SSD1306_FBPIXCLR to save code memory (slower):
//#define SSD1306_FBPIXSET(x,y) ssd1306_fb[SSD1306_FBADDR(x,y)] |= (1 << (y % 8))
//#define SSD1306_FBPIXCLR(x,y) ssd1306_fb[SSD1306_FBADDR(x,y)] &=~ (1 << (y % 8))
#ifdef SSD1306_ENABLE_FRAMEBUFFER
uint8_t ssd1306_fbdata[SSD1306_FBSIZE + 1];
uint8_t *ssd1306_fb;
#endif

void ssd1306_initialize(uint8_t address)
{
    const uint8_t command[] = {
        SSD1306_COMMAND_MODE,
        SSD1306_DISPLAYOFF,
        SSD1306_SETDISPLAYCLOCKDIV,
        0x80,
        SSD1306_SETMULTIPLEX,
        0x3f,
        SSD1306_SETDISPLAYOFFSET,
        0x00,
        SSD1306_SETSTARTLINE | 0x0,
        SSD1306_CHARGEPUMP,
        0x14,
        SSD1306_MEMORYMODE,
        0x00,
        SSD1306_SEGREMAP | 0x1,
        SSD1306_COMSCANDEC,
        SSD1306_SETCOMPINS,
        0x12,
        SSD1306_SETCONTRAST,
        0xcf,
        SSD1306_SETPRECHARGE,
        0xf1,
        SSD1306_SETVCOMDETECT,
        0x40,
        SSD1306_DISPLAYALLON_RESUME,
        SSD1306_NORMALDISPLAY,
        SSD1306_DISPLAYON
    };

    ssd1306_i2c_addr = address;

#ifdef SSD1306_ENABLE_FRAMEBUFFER
    ssd1306_fbdata[0] = SSD1306_DATA_MODE;
    ssd1306_fb = &(ssd1306_fbdata[1]);
#endif

    i2c_write_buf(ssd1306_i2c_addr, command, sizeof(command) );
}

void ssd1306_addr(uint8_t c, uint8_t p)
{
    uint8_t command[] = {
        SSD1306_COMMAND_MODE,
        SSD1306_COLUMNADDR, c, SSD1306_COLUMNS - 1,
        SSD1306_PAGEADDR, p, SSD1306_PAGES - 1
    };
    i2c_write_buf(ssd1306_i2c_addr, command, sizeof(command));
}

#ifdef SSD1306_ENABLE_FRAMEBUFFER
#ifndef SSD1306_FBPIXSET
void SSD1306_FBPIXSET(uint8_t x, uint8_t y)
{
    ssd1306_fb[SSD1306_FBADDR(x,y)] |= (1 << (y % 8));
}
#endif

#ifndef SSD1306_FBPIXCLR
void SSD1306_FBPIXCLR(uint8_t x, uint8_t y)
{
    ssd1306_fb[SSD1306_FBADDR(x,y)] &=~ (1 << (y % 8));
}
#endif

void ssd1306_clear()
{
    memset(ssd1306_fb, 0, SSD1306_FBSIZE);
}

void ssd1306_update()
{
    ssd1306_addr(0, 0);

    i2c_write_buf(ssd1306_i2c_addr, ssd1306_fbdata, sizeof(ssd1306_fbdata));
}

void ssd1306_setPixel(int16_t x, int16_t y, uint8_t color)
{
    if(color == 0)
    {
        SSD1306_FBPIXCLR(x,y);
    }
    else
    {
        SSD1306_FBPIXSET(x,y);
    }
}

void ssd1306_write(uint8_t row, uint8_t col, char *buf)
{
    uint16_t i, len = strlen(buf);
    uint8_t p;
    uint8_t *data = &(ssd1306_fb[SSD1306_FBADDR(SSD1306_FONT_WIDTH*col,SSD1306_PAGES*row)]);

    for(i = 0; i < len; i++)
    {
        for (p = 0; p < SSD1306_FONT_WIDTH; p++)
        {
            data[0] |= fontData[buf[i] - 0x20][p];
            data++;
        }
    }
}

#ifdef SSD1306_ENABLE_GRAPHIC
int16_t ssd1306_abs(int16_t x)
{
    if( x < 0 )
        return -1 * x;
    else
        return x;
}

void ssd1306_line(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color)
{
    //Algorithm has been sourced from:
    //https://de.wikipedia.org/wiki/Bresenham-Algorithmus

    int16_t dx =  ssd1306_abs(x1 - x0);
    int16_t sx = (x0 < x1) ? 1 : -1;
    int16_t dy = -1 * ssd1306_abs(y1 - y0);
    int16_t sy = (y0 < y1) ? 1 : -1;
    int16_t err = dx + dy;
    int16_t e2;

    while(1)
    {
        if(color == 0)
        {
            SSD1306_FBPIXCLR(x0, y0);
        }
        else
        {
            SSD1306_FBPIXSET(x0, y0);
        }
        if ((x0 == x1) && (y0 == y1))
        {
            break;
        }
        e2 = 2 * err;
        if (e2 > dy)
        {
            err += dy;
            x0 += sx;
        }
        if (e2 < dx)
        {
            err += dx;
            y0 += sy;
        }
    }
}

void ssd1306_box(int16_t x0, int16_t x1, int16_t y0, int16_t y1, uint8_t color)
{
    ssd1306_line(x0, y1, x1, y1, color);
    ssd1306_line(x1, y1, x1, y0, color);
    ssd1306_line(x1, y0, x0, y0, color);
    ssd1306_line(x0, y0, x0, y1, color);
}
#endif
#else
void ssd1306_clear()
{
    uint8_t x, y;
    const uint8_t data[] = { SSD1306_DATA_MODE, 0 };

    ssd1306_addr(0, 0);

    for (y = 0; y < SSD1306_PAGES; y++)
    {
        for (x = 0; x < SSD1306_COLUMNS; x++)
        {
            i2c_write_buf(ssd1306_i2c_addr, data, sizeof(data) );
        }
    }
}

void ssd1306_write(uint8_t row, uint8_t col, char *buf)
{
    uint16_t i, p, len = strlen(buf);
    uint8_t data[SSD1306_FONT_WIDTH+1];
    data[0] = SSD1306_DATA_MODE;

    ssd1306_addr(SSD1306_FONT_WIDTH*col, row);

    for(i = 0; i < len; i++)
    {
        for (p = 0; p < SSD1306_FONT_WIDTH; p++)
        {
            data[p+1] = fontData[buf[i] - 0x20][p];
        }
        i2c_write_buf(ssd1306_i2c_addr, data, sizeof(data) );
    }
}
#endif
