//-----------------------------------------------------------------------------
///
/// \brief  Newhaven Display
///
///         SPI, Chip-Select and Data-Command wires needed.
///
/// \date   20151226
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


//---Includes------------------------------------------------------------------


//---General--------------------------

#include <string.h>     // strlen
//#include <unistd.h>     // sleep

//---Own------------------------------

#include <biwak/gdisplay.h>
#include <biwak/output.hpp>
#include <biwak/biwak.h>
#include <biwak/timer.hpp>      // delay
#include <biwak/spi.hpp>
#include <lepto/log.h>
#include <lepto/tools.h>
#include <biwak/font.h>
#include <math.h>
#include <stdlib.h>
//#include <cstdlib>
namespace std
{
   //int abs(a,b)
   //{
   //   return(a)
   //}
}

//---Defines-------------------------------------------------------------------

//#define RGBMAX    0x3F
#define RGBMAX    0xFF

//constexpr const TRgb black  = {.red=0,    .green=0,   .blue=0};
constexpr const TRgb blue   = {.red=0,    .green=0,   .blue=RGBMAX};
constexpr const TRgb green  = {.red=0,    .green=RGBMAX,   .blue=0};
constexpr const TRgb red    = {.red=RGBMAX,    .green=0, .blue=0};
constexpr const TRgb yellow = {.red=RGBMAX,    .green=RGBMAX, .blue=0};
constexpr const TRgb white  = {.red=RGBMAX,    .green=RGBMAX, .blue=RGBMAX};
constexpr const TRgb magenta= {.red=RGBMAX,    .green=0, .blue=RGBMAX};
constexpr const TRgb cyan   = {.red=0,    .green=RGBMAX,   .blue=RGBMAX};
constexpr const TRgb grey   = {.red=RGBMAX/2, .green=RGBMAX/2, .blue=RGBMAX/2};

constexpr const TRgb lightGreen = {.red=RGBMAX/2, .green=RGBMAX,  .blue=RGBMAX/2};
constexpr const TRgb lightBlue = {.red=RGBMAX/2, .green=RGBMAX/2,  .blue=RGBMAX};
constexpr const TRgb lightRed = {.red=RGBMAX, .green=RGBMAX/2,         .blue=RGBMAX/2};
constexpr const TRgb lightGrey = {.red=RGBMAX/2, .green=RGBMAX/2,         .blue=RGBMAX/2};

constexpr const TRgb darkRed   = {.red=RGBMAX/2,  .green=0      , .blue=0  };
constexpr const TRgb darkGreen = {.red=0,       .green=RGBMAX / 2, .blue=0  };
constexpr const TRgb darkBlue  = {.red=0,     .green=0       , .blue=RGBMAX / 2};
constexpr const TRgb bordeaux  = {.red=0x51,  .green=0x10 / 2, .blue= 0x1e/ 2};
constexpr const TRgb darkGrey  = {.red=RGBMAX / 3,.green=RGBMAX / 3 , .blue=RGBMAX / 3};
constexpr const TRgb darkDarkGrey  = {.red=RGBMAX / 4,.green=RGBMAX / 4 , .blue=RGBMAX / 4};
constexpr const TRgb darkDarkViolet = {.red=RGBMAX / 6,.green=0 , .blue=RGBMAX / 8};



//---Implementation------------------------------------------------------------


bool operator==(const TRgb &a, const TRgb &b)
{
   return(
         //   (a.red==b.red)
         //&& (a.green==b.green)
         //&& (a.blue==b.blue)
         memcmp(&a, &b, sizeof(a) ) == 0
   );
}


/// \brief  Le contructeur
///
CGraphicDisplay::CGraphicDisplay( int width, int height )
   :drawBackground(true)
   ,pixelStream(EPixelStream::stopped)
{
   fg=blue;
   bg=black;

   m_xSize=width;
   m_ySize=height;

   mirrored = false;
   return;
}


CGraphicDisplay::~CGraphicDisplay()
{

}


void CGraphicDisplay::gotoxy(int x, int y, bool translate /* =true */ )
{
   xPos= ( mirrored && translate ) ? m_xSize - x : x;
   yPos=y;

   gotoxyHw();

   return;
}


void CGraphicDisplay::prepareLine(const TRgb color, int size)
{
   // if(!memcmp(&lineColor, &color, sizeof(color)))
   //if( ( color == lineColor ) && ( size <= lineSize ) )
   //   return;

   for(int i=0;i<size;i++)
      m_line[i]=color;

   lineColor=color;
   lineSize=size;
}

/// \brief  draws an bar with color transition
///
///         Two absolute coordinates, start and end color, go.
void CGraphicDisplay::drawBar(int x1, int y1, int x2, int y2, const TRgb startColor, const TRgb endColor)
{
   TRgb color=startColor;
   int ys=(y2-y1)+1;
   int xs=(x2-x1)+1;
   constexpr int shift=16;
   // The shifting is done to multiply the values in order to avoid floats
   int redDelta  =( ( (endColor.red  -startColor.red  ) << shift ) / ys );
   int greenDelta=( ( (endColor.green-startColor.green) << shift ) / ys );
   int blueDelta =( ( (endColor.blue -startColor.blue ) << shift ) / ys );
   int sp=0;

#if defined ( USE_FULL_ASSERT___B )
   if( startColor == endColor )
   {
      printf("Warning: transition drawBar() is used with same colors\n");
   }
#endif

   setFrame(x1,y1,x2,y2);
   pixelStreamStart();
   for(int yp=y1; yp<=y2; yp++)
   {
      color.red=  startColor.red  + ( ( redDelta   * sp ) >> shift );
      color.green=startColor.green+ ( ( greenDelta * sp ) >> shift );
      color.blue= startColor.blue + ( ( blueDelta  * sp ) >> shift );

      prepareLine(color, xs);

      #if 0 // no support for setFrame?
      if(mirrored)
         gotoxy(xSize-1-x2, yp, false);
      else
         gotoxy(x1, yp, false);
      #endif

      drawPixels( &m_line[0], xs );
      sp++;
   }
   pixelStreamStop();
}


/// \brief  draws an bar with color transition
///
///         Two absolute coordinates, start and end color, go.
void CGraphicDisplay::drawBar(int x1, int y1, int x2, int y2, const TRgb color)
{
   int xs=(x2-x1)+1;
   prepareLine(color, xs);

   setFrame(x1,y1,x2,y2);
   pixelStreamStart();
   for(int yp=y1; yp<=y2; yp++)
   {
      drawPixels( &m_line[0], xs );
   }
   pixelStreamStop();
}


/// \brief  clears screen with background
///
void CGraphicDisplay::clear()
{
   for(int i=0;i<m_xSize;i++)
      m_line[i]=bg;

   for(int yp=0; yp<m_ySize; yp++)
   {
      gotoxy( mirrored ? m_xSize : 0, yp, false);
      drawPixels( &m_line[0], m_xSize );
   }
}


void CGraphicDisplay::clear(TRgb color)
{
   for(int i=0;i<m_xSize;i++)
      m_line[i]=color;
   
   setFrame( 0, 0, m_xSize-1, m_ySize-1);
   pixelStreamStart();
   for(int yp=0; yp<m_ySize; yp++)
   {
      //lInfo("%dx%d mirrored: %d", m_xSize, m_ySize, (int)mirrored);
      //gotoxy( mirrored ? m_xSize : 0, yp, false);
      //setFrame( 0, yp, m_xSize-1, yp);
      //setFrame( 0, yp, m_xSize-1, yp);
      //( m_xSize - 1 )
      drawPixels( &m_line[0], m_xSize );
   }
   pixelStreamStop();
}

/// \brief  Draws an matrix on the display
///
///         A matrix can be a font for example; array of characters, one
///         character per line
void CGraphicDisplay::drawMatrix(int x, int y, int xSize, int ySize, int fontBytesPerLine, const void *matrix)
{
   uint32_t lineMatrix;

   if(!drawBackground)
   {
      prepareLine(fg, xSize);
   }
   else
   {
      // I will 'destroy' the line; invalidate it
      lineSize=0;
   }
   /*
   setFrame(x,y,x+xSize-1, y+ySize-1);
   if(drawBackground)
   {
      pixelStreamStart();
   }
   */
   //setFrame(0,0,m_xSize-1, m_ySize-1);
   if(drawBackground)
   {
      setFrame(x, y, x+xSize-1, y+ ySize-1);
      pixelStreamStart();
   }
   
   for(int line=0; line<ySize; line++)
   {
      if(fontBytesPerLine==4)
         lineMatrix=( ( (uint32_t *)matrix)[line] );
      else if(fontBytesPerLine==2)
         lineMatrix=( ( (uint16_t *)matrix)[line] );
      else
         lineMatrix=( ( (uint8_t *)matrix)[line] );

      //gotoxy(  x, y+line);

      if(drawBackground)
      {
         //setFrame(x,y+line,x+xSize-1, y+line);
         //pixelStreamStart();
         int xp=(xSize-1);
         for(int col=0; col<xSize; col++)
         {
            m_line[xp--]= ( lineMatrix & 1 ) ? fg : bg;
            lineMatrix>>=1;
         }
         drawPixels( &m_line[0], xSize );
         //pixelStreamStop();
      }
      else
      {
         //lFatal("not implemented");
         for(int col=0; col<xSize; col++)
         {
            // gotoxy(  x+col, y+line);
            uint32_t pix=( lineMatrix >> ((xSize-1)-col)) & 1;
            int size=1;

            while(
                  ( (( lineMatrix >> ((xSize-1)-(col+size))) & 1) == pix )
               && (col+size<xSize) )
            {
               size++;
            }

            if(pix)
            {
               setFrame(x+col,y+line,x+col+size-1, y+line);
               drawPixels( &m_line[0], size );
            }
            col+=size-1;
         }
      }
   }
   if(drawBackground)
   {
      pixelStreamStop();
   }
}


/// \brief  draws string on display
///
///         One absolute coordinates, text, go.
void CGraphicDisplay::drawText(int x, int y, const char *text, int eTextFlags /* =eTextFlags */)
{
   int pos=0;
   int xPos=x;
   const TMatrix *character;

   if (eTextFlags & eTextFlagsHCenter)
      xPos-=textWidth(text)/2;
   else if (eTextFlags & eTextFlagsHRAlign)
      xPos-=textWidth(text);

   if (eTextFlags & eTextFlagsVCenter)
      y-=fontBaseYSize()/2;

   while ( text[pos] )
   {
      character=findCharacter(text[pos]);
      if(character)
      {
         //drawMatrix(xPos + (( 8 - character->xSize) / 2 ), y, character->data);
         drawMatrix(xPos , y, character->xSize, character->ySize, fontBytesPerLine(), character->data);
         //xPos+=character->xSize+2;
         xPos+=( character->xSize + fontSpacing() );
      }
      else
         xPos+=( fontBaseXSize() + fontSpacing() );

      pos++;
   }

   return;
}


void CGraphicDisplay::drawLines(int x, int y, const char* const text[])
{
   int lines=0;
   while( text[lines] )
   {
      lines++;
   }
   int lineDist=fontBaseYSize() + 5;
   int ySize=fontBaseYSize() + ( ( lines -1 ) * lineDist );
   int yStart=y - ( ySize / 2 );

   for(int i1=0; i1<lines; i1++)
   {
      drawText(x, yStart + ( i1 * lineDist ) , text[i1],
                eTextFlagsHCenter);
   }
   return;
}


void CGraphicDisplay::drawCircle(int x0, int y0, int radius, bool filled, const TRgb &color)
{
   // This was measured with the ili display controller;
   // With an radius above 38 the hline method is faster
   if( (radius>38) || ( !filled ) || ( !drawBackground ) )
   {
      drawCircleHLine(x0, y0, radius, filled, color);
   }
   else
   {
      // this implementation does not work well yet with unfilled circles.
      drawCircleBlock(x0, y0, radius, filled, color);
   }
}


void CGraphicDisplay::drawCircleHLine(int x0, int y0, int radius, bool filled, const TRgb &color)
{
    int x = radius;
    int y = 0;
    int xChange = 1 - (radius << 1);
    int yChange = 0;
    int radiusError = 0;
    while (x >= y)
    {
        if(filled)
        {
           drawHLine(x0 - x, y0 + y, x0 + x, color);
           drawHLine(x0 - x, y0 - y, x0 + x, color);

           drawHLine(x0 - y, y0 + x, x0 + y, color);
           drawHLine(x0 - y, y0 - x, x0 + y, color);
        }
        else
        {
           drawPixel(x0 - x, y0 + y, color);
           drawPixel(x0 - x, y0 - y, color);
           drawPixel(x0 + x -1, y0 + y, color);
           drawPixel(x0 + x -1, y0 - y, color);
           drawPixel(x0 - y, y0 + x, color);
           drawPixel(x0 - y, y0 - x, color);
           drawPixel(x0 + y -1, y0 + x, color);
           drawPixel(x0 + y -1, y0 - x, color);
        }
        y++;
        radiusError += yChange;
        yChange += 2;
        if (((radiusError << 1) + xChange) > 0)
        {
            x--;
            radiusError += xChange;
            xChange += 2;
        }
    }
}


void CGraphicDisplay::drawCircleBlock(int x0, int y0, int radius, bool filled, const TRgb &color)
{
    int x = radius;
    int y = 0;
    int xChange = 1 - (radius << 1);
    int yChange = 0;
    int radiusError = 0;
    int xs=(radius*2)+1;
    int dist[120+1];

    memset(dist, 0, sizeof(dist));

    setFrame(x0-radius, y0-radius,x0+radius, y0+radius);
    pixelStreamStart();
    while (x >= y)
    {
        //if(filled)
        {
           dist[radius-y]=x;
           dist[radius-x]=y;
        }
        y++;
        radiusError += yChange;
        yChange += 2;
        if (((radiusError << 1) + xChange) > 0)
        {
            x--;
            radiusError += xChange;
            xChange += 2;
        }
    }
    //prepareLine(bg, xs);
    for(int i1=0; i1<xs; i1++)
    {
       //if(i1>radius)
          prepareLine(bg, xs);

        int xdist;
        if(i1>=radius)
           xdist=dist[radius-(i1-radius)];
        else
           xdist=dist[i1];
        int start=radius-xdist;
        
        if(filled)
        //if(1)
        {
           for(int i2=0; i2<(xdist*2)+1; i2++)
           {
              m_line[ start+i2]=color;
           }
        }
        else
        {
           m_line[ start ]=color;
           m_line[ start + (xdist*2) ]=color;
        }
        lineSize=0;
        drawPixels(m_line, xs);
    }
   pixelStreamStop();
}

void CGraphicDisplay::drawLine(int x0, int y0, int x1, int y1, const TRgb &color)
{
   pixelStreamStart();
    bool steep = false;
    if ( abs(x0-x1) < abs(y0-y1) ) {
        SWAP(x0, y0);
        SWAP(x1, y1);
        steep = true;
    }
    if (x0>x1) {
        SWAP(x0, x1);
        SWAP(y0, y1);
    }
    int dx = x1-x0;
    int dy = y1-y0;
    float derror = abs( dy / float(dx) );
    float error = 0;
    int y = y0;
    for (int x=x0; x<=x1; x++) {
        if (steep) {
           drawPixel( y, x, color );
        } else {
           drawPixel( x, y, color );
        }
        error += derror;
        if (error>.5) {
            y += (y1>y0?1:-1);
            error -= 1.;
        }
    }
    pixelStreamStop();
}

void CGraphicDisplay::drawHLine(int x0, int y0, int x1, const TRgb &color)
{
   int xs=(x1-x0)+1;
   setFrame(x0,y0,x1,y0);
   //gotoxy(x0, y0);
   prepareLine(color, xs);
   drawPixels( m_line, xs);
}

void CGraphicDisplay::drawVLine(int x0, int y0, int y1, const TRgb &color)
{
   int ys=(y1-y0)+1;
   setFrame(x0, y0, x0, y1);
   //pixelStreamStart();
   (void)ys;
   prepareLine(color, ys);
   //drawPixels( m_line, ys);
   //lInfo("ys: %d", ys);
   drawPixels( m_line, ys);
   
   //pixelStreamStop();
}

void CGraphicDisplay::drawRect(int x0, int y0, int x1, int y1, const TRgb &color)
{
   drawHLine(x0,y0,x1, color);
   drawVLine(x1,y0,y1, color);
   drawHLine(x0,y1,x1, color);
   drawVLine(x0,y0,y1, color);
}

void CGraphicDisplay::drawPixel(int x, int y, const TRgb &color )
{
   //gotoxy(x,y);
   setFrame(x, y, x, y);
   drawPixel( color );
}


//---fin-----------------------------------------------------------------------
