//
// AVR TFT screen graphical library
// Homelab library
//
// Department of Mechatronics
// Tallinn University of Technology
// ITT Group
// © Copyrights 2014
//
#if defined (__AVR_ATxmega128A1U__)

// Graphics library by ladyada/adafruit with init code from Rossum
// MIT license
#include "xmega/clksys_driver.h"
#include "module/lcd_gfx.h"

void spi_write(uint8_t map)
{
    SPIE_DATA = map;
    while(!(SPIE.STATUS & (1<<SPI_IF_bp)));
}

void writecommand(uint8_t c)
{
    LCD_SELECT();
    LCD_CMD();

    spi_write(c);

    LCD_UNSELECT();
}

void writedata(uint8_t c)
{
    LCD_SELECT();
    LCD_DRAM();

    spi_write(c);

    LCD_UNSELECT();
}

void setAddrWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
{
    writecommand(ST7735_CASET);  // column addr set
    writedata(0x00);
    writedata(x0);   // XSTART
    writedata(0x00);
    writedata(x1);   // XEND

    writecommand(ST7735_RASET);  // row addr set
    writedata(0x00);
    writedata(y0);    // YSTART
    writedata(0x00);
    writedata(y1);    // YEND

    writecommand(ST7735_RAMWR);  // write to RAM
}

void lcd_gfx_fillScreen(uint16_t color)
{
    setAddrWindow(0, 0, width-1, height-1);

    LCD_DRAM() ;
    LCD_SELECT();
    uint8_t high_color = ((color >> 8) & 0xFF);
    uint8_t low_color = color & 0xFF;

    for (uint8_t x=0; x < width; x++)
    {
        for (uint8_t y=0; y < height; y++)
        {
            spi_write(high_color);
            spi_write(low_color);
        }
    }

    LCD_UNSELECT();
}

/*! \brief SPI master module on PORT C. */
SPI_Master_t spiMasterE;

void lcd_gfx_init(void)
{
    if(_sys_freq == 2)
        Homelab_clock_init();

    PORTK.DIRSET |= (0x0F); //LCD CS, RS, RST, BL as output
    PORTE.DIRSET |= (1<<LCD_SDA)|(1<<LCD_SCL)|(1<<4);		//LCD SDA, SCL as output
    LCD_RESET();
    LCD_UNSELECT();

    /* Initialize SPI master on port C. */

    SPI_MasterInit(	&spiMasterE,
                    &SPIE,
                    &PORTE,
                    false,
                    SPI_MODE_0_gc,
                    SPI_INTLVL_OFF_gc,
                    true,
                    SPI_PRESCALER_DIV4_gc);

    writecommand(ST7735_SWRESET);	// software reset
    _delay_ms(10);
    writecommand(ST7735_SLPOUT);	// out of sleep mode
    _delay_ms(10);

    writecommand(ST7735_COLMOD);	// set color mode
    writedata(0x05);				// 16-bit color
    _delay_us(400);

    writecommand(ST7735_FRMCTR1);	// frame rate control
    writedata(0x00);				// fastest refresh
    writedata(0x06);				// 6 lines front porch
    writedata(0x03);				// 3 lines backporch
    _delay_us(400);

    writecommand(ST7735_MADCTL);  // memory access control (directions)
    writedata(0xC8);          // row address/col address, bottom to top refresh

    writecommand(ST7735_DISSET5); // display settings #5
    writedata(0x15);          // 1 clock cycle nonoverlap, 2 cycle gate rise, 3 cycle oscil. equalize
    writedata(0x02);          // fix on VTL

    writecommand(ST7735_INVCTR);  // display inversion control
    writedata(0x0);           // line inversion

    writecommand(ST7735_GMCTRP1);
    writedata(0x09);
    writedata(0x16);
    writedata(0x09);
    writedata(0x20);
    writedata(0x21);
    writedata(0x1B);
    writedata(0x13);
    writedata(0x19);
    writedata(0x17);
    writedata(0x15);
    writedata(0x1E);
    writedata(0x2B);
    writedata(0x04);
    writedata(0x05);
    writedata(0x02);
    writedata(0x0E);
    writecommand(ST7735_GMCTRN1);
    writedata(0x0B);
    writedata(0x14);
    writedata(0x08);
    writedata(0x1E);
    writedata(0x22);
    writedata(0x1D);
    writedata(0x18);
    writedata(0x1E);
    writedata(0x1B);
    writedata(0x1A);
    writedata(0x24);
    writedata(0x2B);
    writedata(0x06);
    writedata(0x06);
    writedata(0x02);
    writedata(0x0F);
    _delay_us(400);

    writecommand(ST7735_NORON);   // normal display on
    _delay_us(400);

    writecommand(ST7735_DISPON);
    _delay_ms(300);

    PORTK.OUTSET |= (1<<LCD_BL);
    LcdFont((uint8_t *)FONT_SELECTED);
    BkColor = BLACK;
    FgColor = WHITE;
    lcd_gfx_fillScreen(BkColor);	// Background	BLACK
}

void lcd_gfx_clear()
{
    lcd_gfx_fillRect(0,0,MAX_X,MAX_Y,BLACK);
}

void lcd_gfx_backlight(uint8_t state)
{
    if(state)	PORTK.OUTSET |= (1<<LCD_BL);
    else		PORTK.OUTCLR = (1<<LCD_BL);
}

void lcd_gfx_drawPixel(uint8_t x, uint8_t y)
{

    uint16_t color;
    color= GetFgColor();	// backup color

    setAddrWindow(x,y,x+1,y+1);

    // setup for data
    LCD_DRAM() ;
    LCD_SELECT();
    spi_write(color >> 8);
    spi_write(color);
    LCD_UNSELECT();

}
void lcd_gfx_drawPixel_h(uint8_t x, uint8_t y)    // Hintergrund mit aktueller Farbe überschreiben
{
    uint16_t color;
    color= GetBkColor();	// backup color

    if ((x >= GetMaxX()) || (y >= GetMaxY())) return;

    setAddrWindow(x,y,x+1,y+1);
    // setup for data
    LCD_DRAM() ;
    LCD_SELECT();
    spi_write(color >> 8);
    spi_write(color);
    LCD_UNSELECT();

}

// draw a string from memory
void lcd_gfx_write_string(char *c)
{
    while (c[0] != 0)
    {
        DrawChar(c[0]);
        c++;
    }
}

void lcd_gfx_write_char(char c)
{
    DrawChar(c);
}


// fill a circle
void lcd_gfx_fillCircle(uint8_t x0, uint8_t y0, uint8_t r, uint16_t color)
{
    int16_t f = 1 - r;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * r;
    int16_t x = 0;
    int16_t y = r;

    lcd_gfx_drawVerticalLine(x0, y0-r, 2*r+1, color);

    while (x<y)
    {
        if (f >= 0)
        {
            y--;
            ddF_y += 2;
            f += ddF_y;
        }
        x++;
        ddF_x += 2;
        f += ddF_x;

        lcd_gfx_drawVerticalLine(x0+x, y0-y, 2*y+1, color);
        lcd_gfx_drawVerticalLine(x0-x, y0-y, 2*y+1, color);
        lcd_gfx_drawVerticalLine(x0+y, y0-x, 2*x+1, color);
        lcd_gfx_drawVerticalLine(x0-y, y0-x, 2*x+1, color);
    }
}

// draw a circle outline
void lcd_gfx_drawCircle(uint8_t x0, uint8_t y0, uint8_t r,
                        uint16_t color)
{
    int16_t f = 1 - r;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * r;
    int16_t x = 0;
    int16_t y = r;

    lcd_gfx_drawPixel(x0, y0+r);
    lcd_gfx_drawPixel(x0, y0-r);
    lcd_gfx_drawPixel(x0+r, y0);
    lcd_gfx_drawPixel(x0-r, y0);

    while (x<y)
    {
        if (f >= 0)
        {
            y--;
            ddF_y += 2;
            f += ddF_y;
        }
        x++;
        ddF_x += 2;
        f += ddF_x;

        lcd_gfx_drawPixel(x0 + x, y0 + y);
        lcd_gfx_drawPixel(x0 - x, y0 + y);
        lcd_gfx_drawPixel(x0 + x, y0 - y);
        lcd_gfx_drawPixel(x0 - x, y0 - y);

        lcd_gfx_drawPixel(x0 + y, y0 + x);
        lcd_gfx_drawPixel(x0 - y, y0 + x);
        lcd_gfx_drawPixel(x0 + y, y0 - x);
        lcd_gfx_drawPixel(x0 - y, y0 - x);

    }
}

uint8_t getRotation(void)
{
    return madctl;
}

void setRotation(uint8_t m)
{
    madctl = m;
    writecommand(ST7735_MADCTL);  // memory access control (directions)
    writedata(madctl);  // row address/col address, bottom to top refresh
}

// draw a rectangle
void lcd_gfx_drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color)
{
    // smarter version
    lcd_gfx_drawHorizontalLine(x, y, w, color);
    lcd_gfx_drawHorizontalLine(x, y+h-1, w, color);
    lcd_gfx_drawVerticalLine(x, y, h, color);
    lcd_gfx_drawVerticalLine(x+w-1, y, h, color);
}

void lcd_gfx_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color)
{
    // smarter version

    setAddrWindow(x, y, x+w-1, y+h-1);

    // setup for data
    LCD_DRAM() ;
    LCD_SELECT();
    for (x=0; x < w; x++)
    {
        for (y=0; y < h; y++)
        {
            spi_write(color >> 8);
            spi_write(color);
        }
    }
    LCD_UNSELECT();
}

void lcd_gfx_drawVerticalLine(uint8_t x, uint8_t y, uint8_t length, uint16_t color)
{
    if (x >= width) return;
    if (y+length >= height) length = height-y-1;

    lcd_gfx_drawFastLine(x,y,length,color,1);
}

void lcd_gfx_drawHorizontalLine(uint8_t x, uint8_t y, uint8_t length, uint16_t color)
{
    if (y >= height) return;
    if (x+length >= width) length = width-x-1;

    lcd_gfx_drawFastLine(x,y,length,color,0);
}

void lcd_gfx_drawFastLine(uint8_t x, uint8_t y, uint8_t length, uint16_t color, uint8_t rotflag)
{
    if (rotflag)
    {
        setAddrWindow(x, y, x, y+length);
    }
    else
    {
        setAddrWindow(x, y, x+length, y+1);
    }

    LCD_DRAM() ;
    LCD_SELECT();

    while (length--)
    {
        spi_write(color >> 8);
        spi_write(color);
    }
    LCD_UNSELECT();
}


// bresenham's algorithm - thx wikpedia
void lcd_gfx_drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color)
{
    uint16_t steep = abs(y1 - y0) > abs(x1 - x0);
    if (steep)
    {
        swap(x0, y0);
        swap(x1, y1);
    }

    if (x0 > x1)
    {
        swap(x0, x1);
        swap(y0, y1);
    }

    uint16_t dx, dy;
    dx = x1 - x0;
    dy = abs(y1 - y0);

    int16_t err = dx / 2;
    int16_t ystep;

    if (y0 < y1)
    {
        ystep = 1;
    }
    else
    {
        ystep = -1;
    }

    for (; x0<=x1; x0++)
    {
        if (steep)
        {
            lcd_gfx_drawPixel(y0, x0);
        }
        else
        {
            lcd_gfx_drawPixel(x0, y0);
        }
        err -= dy;
        if (err < 0)
        {
            y0 += ystep;
            err += dx;
        }
    }
}


void get_cursor_xy (uint8_t x, uint8_t y)
{
    setAddrWindow(x,y,x+1,y+1);
}
/*********************************************************************
* Function    : void DrawSymbol(unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned char t, unsigned char *pImage)
* Description : display image array to lcd
* Input       : x,y    - pixel coordinates
*             : w      - width
*             : h      - height
*             : t      - compress type(0 = none(RGB565), 1 = compress(RGB5<compress bit>55)
*		: pImage - FLASH array of image
* Output      : none
* Note        : none
********************************************************************/
void lcd_gfx_drawSymbol(unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned char t, const unsigned char *pImage)
{
    unsigned int x1,y1;

    // set draw area
    setAddrWindow(x,y,x+w-1,y+h-1);
    FgColor = WHITE;
    for (y1 = 0; y1<h ; y1++)
    {
        for (x1 = 0; x1<w ; x1++)
        {
            //if(pImage[0])
        }
    }
    setAddrWindow(0, 0, GetMaxX(), GetMaxY());
}

void pushColor(uint16_t color)
{
    LCD_DRAM();
    LCD_SELECT();

    spi_write(color >> 8);
    spi_write(color);

    LCD_UNSELECT();
}


/*********************************************************************
* Function    : void DrawImage(unsigned int x, unsigned int y, unsigned char *pImage)
* Description : display image array to lcd
* Input       : x,y    - pixel coordinates
*			  : pImage - FLASH array of image
* Output      : none
* Note        : ** modify image format here **
*   image format
*   <type 1 byte><width 2 byte><height 2 btye><image array n byte>
********************************************************************/
FIL Bmp_obj;			/* File object needed for each open file */
FATFS BmpFS_Obj;
void lcd_gfx_drawImage(unsigned int x, unsigned int y, const TCHAR* filename)
{
    //File     bmpFile;
    int      bmpWidth, bmpHeight;   // W+H in pixels
    uint8_t  bmpDepth;              // Bit depth (currently must be 24)
    uint32_t bmpImageoffset;        // Start of image data in file
    uint32_t rowSize;               // Not always = bmpWidth; may have padding
    uint8_t  sdbuffer[60];			// pixel buffer (R+G+B per pixel)
    uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
    uint8_t  flip    = true;        // BMP is stored bottom-to-top
    int      w, h, row, col;
    uint8_t  r, g, b;
    uint32_t pos = 0;

    uint8_t temp;

    if((x >= width) || (y >= height)) return;
    disk_initialize(0);
    temp = f_mount(0,&BmpFS_Obj);

    temp = f_open(&Bmp_obj,filename,FA_READ);
    // Open requested file on SD card
    if (temp != FR_OK)
        return;

    // Parse BMP header
    if(read16() == 0x4D42)  // BMP signature
    {
        read32();
        read32(); // Read & ignore creator bytes
        bmpImageoffset = read32(); // Start of image data

        // Read DIB header
        read32();
        bmpWidth  = read32();
        bmpHeight = read32();
        if(read16() == 1)  // # planes -- must be '1'
        {
            bmpDepth = read16(); // bits per pixel
            if((bmpDepth == 24) && (read32() == 0))  // 0 = uncompressed
            {
                // BMP rows are padded (if needed) to 4-byte boundary
                rowSize = (bmpWidth * 3 + 3) & ~3;

                // If bmpHeight is negative, image is in top-down order.
                // This is not canon but has been observed in the wild.
                if(bmpHeight < 0)
                {
                    bmpHeight = -bmpHeight;
                    flip      = false;
                }

                // Crop area to be loaded
                w = bmpWidth;
                h = bmpHeight;
                if((x+w-1) >= width)  w = width  - x;
                if((y+h-1) >= height) h = height - y;

                // Set TFT address window to clipped image bounds
                setAddrWindow(x, y, x+w-1, y+h-1);

                for (row=0; row<h; row++)  // For each scanline...
                {
                    // Seek to start of scan line.  It might seem labor-
                    // intensive to be doing this on every line, but this
                    // method covers a lot of gritty details like cropping
                    // and scanline padding.  Also, the seek only takes
                    // place if the file position actually needs to change
                    // (avoids a lot of cluster math in SD library).
                    if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
                        pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
                    else     // Bitmap is stored top-to-bottom
                        pos = bmpImageoffset + row * rowSize;
                    if(f_tell(&Bmp_obj) != pos)  // Need seek?
                    {
                        f_lseek(&Bmp_obj,pos);
                        buffidx = sizeof(sdbuffer); // Force buffer reload
                    }

                    for (col=0; col<w; col++)  // For each pixel...
                    {
                        // Time to read more pixel data?
                        if (buffidx >= sizeof(sdbuffer))  // Indeed
                        {
                            f_read(&Bmp_obj,sdbuffer, sizeof(sdbuffer),0);
                            buffidx = 0; // Set index to beginning
                        }

                        // Convert pixel from BMP to TFT format, push to display
                        b = sdbuffer[buffidx++];
                        g = sdbuffer[buffidx++];
                        r = sdbuffer[buffidx++];
                        pushColor(Color565(r,g,b));
                    } // end pixel
                } // end scanline
            } // end goodBmp
        }
    }
    f_close(&Bmp_obj);
}

uint16_t read16()
{
    uint16_t result;
    uint8_t buffer[2];
    f_read(&Bmp_obj,buffer,2, 0);
    ((uint8_t *)&result)[0] = buffer[0]; // LSB
    ((uint8_t *)&result)[1] = buffer[1]; // MSB
    return result;
}

uint32_t read32()
{
    uint32_t result;
    uint8_t buffer[4];
    f_read(&Bmp_obj,buffer,4, 0);

    ((uint8_t *)&result)[0] = buffer[0]; // LSB
    ((uint8_t *)&result)[1] = buffer[1];
    ((uint8_t *)&result)[2] = buffer[2];
    ((uint8_t *)&result)[3] = buffer[3]; // MSB
    return result;
}


/* End Of File ---------------------------------------------------------------*/

#endif