/**
 *  GraphLCD driver library
 *
 *  st7565-reel.c - Plugin for ST7565 display on Reelbox
 *                  driven by an AVR on the front panel
 *
 *  (c) 2004 Georg Acher, BayCom GmbH, http://www.baycom.de
 *      based on simlcd.c by Carsten Siebholz
 **/

/***************************************************************************
 *                                                                         *
 *   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 2 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, write to the Free Software Foundation, Inc.,                  *
 *   59 Temple Place, Suite 330, Boston, MA  02111-1307  USA               *
 *                                                                         *
 ***************************************************************************/

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <termios.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <inttypes.h>

#include "common.h"
#include "config.h"

#include "st7565r-reel.h"
#include "port.h"


namespace GLCD
{

cDriverST7565RReel::cDriverST7565RReel(cDriverConfig * config) : cDriver(config)
{
    port = new cSerialPort();
}

cDriverST7565RReel::~cDriverST7565RReel()
{
    delete port;
}

int cDriverST7565RReel::Init(void)
{
    width = config->width;
    if (width <= 0)
        width = 128;
    height = config->height;
    if (height <= 0)
        height = 64;

    // setup lcd array
    LCD = new unsigned char *[(width + 7) / 8];
    if (LCD)
    {
        for (int x = 0; x < (width + 7) / 8; x++)
        {
            LCD[x] = new unsigned char[height];
        }
    }

    if (port->Open(config->device.c_str()) != 0)
        return -1;

    port->SetBaudRate(115200);

    *oldConfig = *config;
    set_displaymode(0);
    // clear display
    Clear();

    syslog(LOG_INFO, "%s: ST7565R initialized.\n", config->name.c_str());
    return 0;
}

int cDriverST7565RReel::DeInit(void)
{
    // free lcd array
    if (LCD)
    {
        for (int x = 0; x < (width + 7) / 8; x++)
        {
            delete[] LCD[x];
        }
        delete[] LCD;
    }

    if (port->Close() != 0)
        return -1;

    return 0;
}


int cDriverST7565RReel::CheckSetup(void)
{
    if (config->device != oldConfig->device ||
        config->width != oldConfig->width ||
        config->height != oldConfig->height)
    {
        DeInit();
        Init();
        return 0;
    }

    if (config->brightness != oldConfig->brightness)
    {
        oldConfig->brightness = config->brightness;
        SetBrightness(config->brightness);
        usleep(500000);
    }

    if (config->contrast != oldConfig->contrast)
    {
        oldConfig->contrast = config->contrast;
        SetContrast(config->contrast);
    }

    if (config->upsideDown != oldConfig->upsideDown ||
        config->invert != oldConfig->invert)
    {
        oldConfig->upsideDown = config->upsideDown;
        oldConfig->invert = config->invert;
        return 1;
    }
    return 0;
}


void cDriverST7565RReel::Clear(void)
{
    for (int x = 0; x < (width + 7) / 8; x++)
        memset(LCD[x], 0, height);
}

void cDriverST7565RReel::SetPixel(int x, int y, uint32_t data)
{
    if (x >= width || y >= height)
        return;

    int pos = x % 8;
    if (config->upsideDown)
    {
        x = width - 1 - x;
        y = height - 1 - y;
    }
    else
        pos = 7 - pos; // reverse bit position

    if (data == GRAPHLCD_White)
        LCD[x / 8][y] |= (1 << pos);
    else
        LCD[x / 8][y] &= ( 0xFF ^ (1 << pos) );
}

void cDriverST7565RReel::Refresh(bool refreshAll)
{
    int x,y,xx,yy;
    int i;
    unsigned char c;
    int rx;
    int invert;

    CheckSetup();

    if (!LCD)
        return;

    invert=(config->invert != 0) ? 0xff : 0x00;

    for (y = 0; y < (height); y+=8)
    {
        display_cmd( 0xb0+((y/8)&15));
        for (x = 0; x < (width + 7) / 8; x+=4)
        {
            unsigned char d[32]={0};

            for(yy=0;yy<8;yy++)
            {
                for (xx=0;xx<4;xx++)
                {
                    c = (LCD[x+xx][y+yy])^invert;

                    for (i = 0; i < 8; i++)
                    {
                        d[i+xx*8]>>=1;
                        if (c & 0x80)
                            d[i+xx*8]|=0x80;
                        c<<=1;
                    }
                }
            }

            rx=4+x*8;
            //    printf("X %i, y%i\n",rx,y);

            display_cmd( 0x10+((rx>>4)&15));
            display_cmd( 0x00+(rx&15));
            display_data( d,32);
        }
    }

// syslog(LOG_INFO, "refresh.\n");
}

void cDriverST7565RReel::Set8Pixels(int x, int y, unsigned char data)
{
    if (!LCD)
        return;

    clip(x, 0, width - 1);
    clip(y, 0, height - 1);

    if (!config->upsideDown)
    {
        // normal orientation
        //    syslog(LOG_INFO, "%s: set8pixel normal.\n");
        LCD[x / 8][y] = LCD[x / 8][y] | data;
    }
    else
    {
        // upside down orientation
        x = width - 1 - x;
        y = height - 1 - y;
        //    syslog(LOG_INFO, "%s: set8pixel flip.\n", LCD);
        LCD[x /8][y] = LCD[x / 8][y] | ReverseBits(data);
    }
}

void cDriverST7565RReel::display_cmd(unsigned char cmd)
{
    unsigned char buf[]={0xa5, 0x05, 3, 0, cmd};
    port->WriteData(buf, 5);
//    syslog(LOG_INFO, "%s: display cmd.\n", cmd);
}

void cDriverST7565RReel::display_data(unsigned char *data, unsigned char l)
{
    if (l > 60)
    {
        syslog(LOG_ERR, "cDriverST7565RReel::display_data buffer length exceeded!");
        return;
    }

    unsigned char buf[64]={0xa5,0x05,(unsigned char)(l+2),+1};
    memcpy(buf+4,data,l);
    port->WriteData(buf, l+4);
//    syslog(LOG_INFO, "%s: display data.\n", data);
}
void cDriverST7565RReel::set_displaymode(unsigned char m)
{
    unsigned char buf[]={0xa5,0x09,m};
    port->WriteData(buf, 3);
//    syslog(LOG_INFO, "displaymode.\n");
}

void cDriverST7565RReel::SetBrightness(unsigned int percent)
{
    unsigned char buf[]={0xa5,0x02, 0x00, 0x00};
    int n=static_cast<int>(percent*2.5);
    if (n>255)
        n=255;
    buf[2]=(char)(n);
    port->WriteData(buf,4);
}

void cDriverST7565RReel::SetContrast(unsigned int val)
{
    unsigned char buf[]={0xa5,0x03, 0x00, 0x00};
    buf[2]=(char)(val*25);
    port->WriteData(buf,4);
}


}
