/*
  xdsp_16_esp32_epaper_47.ino - LILIGO47 e-paper support for Tasmota

  Copyright (C) 2021  Theo Arends, Gerhard Mutz and LILIGO

  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/>.
*/

#ifdef ESP32
#ifdef USE_DISPLAY
#ifdef USE_LILYGO47

#define XDSP_16                16

#define EPD47_BLACK  0
#define EPD47_WHITE 15

#include <epd4in7.h>

Epd47 *epd47;
bool epd47_init_done = false;
extern uint8_t color_type;
extern uint16_t fg_color;
extern uint16_t bg_color;

/*********************************************************************************************/

void EpdInitDriver47(void) {
  if (PinUsed(GPIO_EPD_DATA)) {

    Settings->display_model = XDSP_16;

    if (Settings->display_width != EPD47_WIDTH) {
      Settings->display_width = EPD47_WIDTH;
    }
    if (Settings->display_height != EPD47_HEIGHT) {
      Settings->display_height = EPD47_HEIGHT;
    }

    // init renderer
    epd47  = new Epd47(Settings->display_width, Settings->display_height);
    epd47->Init();

    renderer = epd47;
    renderer->DisplayInit(DISPLAY_INIT_MODE, Settings->display_size, Settings->display_rotate, Settings->display_font);
    renderer->setTextColor(EPD47_BLACK, EPD47_WHITE);

#ifdef SHOW_SPLASH
    // Welcome text
    renderer->setTextFont(2);
    renderer->DrawStringAt(50, 50, "LILGO 4.7 E-Paper Display!", EPD47_BLACK, 0);
    renderer->Updateframe();
#endif

    fg_color = EPD47_BLACK;
    bg_color = EPD47_WHITE;
    color_type = COLOR_COLOR;

#ifdef USE_TOUCH_BUTTONS
    // start digitizer
    EPD47_Touch_Init();
#endif // USE_TOUCH_BUTTONS

    epd47_init_done = true;
    AddLog(LOG_LEVEL_INFO, PSTR("DSP: E-Paper 4.7"));
  }
}

/*********************************************************************************************/


#ifdef USE_TOUCH_BUTTONS

#define TOUCH_SLAVE_ADDRESS 0x5a
class TouchClass {

    typedef struct {
        uint8_t id;
        uint8_t state;
        uint16_t x;
        uint16_t y;
    } TouchData_t;

public:
    bool    begin(TwoWire &port = Wire, uint8_t addr = TOUCH_SLAVE_ADDRESS);
    uint8_t scanPoint();
    void    getPoint(int16_t &x, int16_t &y, uint8_t index);
//    void    sleep(void);
//    void    wakeup(void);
    TouchData_t data[5];

private:
    void    clearFlags(void);
    void    readBytes(uint8_t *data, uint8_t nbytes);
    uint8_t _address;
    bool initialization = false;
    TwoWire *_i2cPort;
};

void TouchClass::readBytes(uint8_t *data, uint8_t nbytes) {
    _i2cPort->beginTransmission(_address);  // Initialize the Tx buffer
    _i2cPort->write(data, 2);                // Put data in Tx buffer
    if (0 != _i2cPort->endTransmission()) {
        Serial.println("readBytes error!");
    }
    uint8_t i = 0;
    _i2cPort->requestFrom(_address, nbytes);  // Read bytes from slave register address
    while (_i2cPort->available()) {
        data[i++] = _i2cPort->read();
    }
}

void TouchClass::clearFlags(void) {
    uint8_t buf[3] = {0xD0, 0X00, 0XAB};
    _i2cPort->beginTransmission(_address);
    _i2cPort->write(buf, 3);
    _i2cPort->endTransmission();
}

bool TouchClass::begin(TwoWire &port, uint8_t addr) {
    _i2cPort = &port;
    _address = addr;
    _i2cPort->beginTransmission(_address);
    if (0 == _i2cPort->endTransmission()) {
      //  wakeup();
        return true;
    }
    return false;
}

uint8_t TouchClass::scanPoint() {
    uint8_t point = 0;
    uint8_t buffer[40] = {0};
    uint32_t sumL = 0, sumH = 0;

    buffer[0] = 0xD0;
    buffer[1] = 0x00;
    readBytes(buffer, 7);

    if (buffer[0] == 0xAB) {
        clearFlags();
        return 0;
    }

    point = buffer[5] & 0xF;

    if (point == 1) {
        buffer[5] = 0xD0;
        buffer[6] = 0x07;
        readBytes( &buffer[5], 2);
        sumL = buffer[5] << 8 | buffer [6];

    } else if (point > 1) {
        buffer[5] = 0xD0;
        buffer[6] = 0x07;
        readBytes( &buffer[5], 5 * (point - 1) + 3);
        sumL = buffer[5 * point + 1] << 8 | buffer[5 * point + 2];
    }
    clearFlags();

    for (int i = 0 ; i < 5 * point; ++i) {
        sumH += buffer[i];
    }

    if (sumH != sumL) {
        point = 0;
    }
    if (point) {
        uint8_t offset;
        for (int i = 0; i < point; ++i) {
            if (i == 0) {
                offset = 0;
            } else {
                offset = 4;
            }
            data[i].id =  (buffer[i * 5 + offset] >> 4) & 0x0F;
            data[i].state = buffer[i * 5 + offset] & 0x0F;
            if (data[i].state == 0x06) {
                data[i].state = 0x07;
            } else {
                data[i].state = 0x06;
            }
            data[i].y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F));
            data[i].x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F));
        }
    } else {
        point = 1;
        data[0].id = (buffer[0] >> 4) & 0x0F;
        data[0].state = 0x06;
        data[0].y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F));
        data[0].x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F));
    }
    // Serial.printf("X:%d Y:%d\n", data[0].x, data[0].y);
    return point;
}

void TouchClass::getPoint(int16_t &x, int16_t &y, uint8_t index) {
    if (index >= 4)return;
    x = data[index].x;
    y = data[index].y;
}

//if (touch.scanPoint()) {
  //          touch.getPoint(x, y, 0);

#define EPD47_address 0x5A

TouchClass *EPD47_touchp;

void EPD47_Touch_Init(void) {
FT5206_found = false;
EPD47_touchp = new TouchClass();
  if (EPD47_touchp->begin(Wire, EPD47_address)) {
    I2cSetActiveFound(EPD47_address, "EPD47");
    FT5206_found = true;
  }
}

uint8_t EPD47_ctouch_counter = 0;
// no rotation support
void EPD47_RotConvert(int16_t *x, int16_t *y) {
int16_t temp;
  if (renderer) {
    uint8_t rot=renderer->getRotation();
    switch (rot) {
      case 0:
        break;
      case 1:
        temp=*y;
        *y=renderer->height()-*x;
        *x=temp;
        break;
      case 2:
        *x=renderer->width()-*x;
        *y=renderer->height()-*y;
        break;
      case 3:
        temp=*y;
        *y=*x;
        *x=renderer->width()-temp;
        break;
    }
  }
}

// check digitizer hit
void EPD47_CheckTouch(void) {
  EPD47_ctouch_counter++;
  if (2 == EPD47_ctouch_counter) {
    // every 100 ms should be enough
    EPD47_ctouch_counter = 0;
    touched = EPD47_touchp->scanPoint();
    if (touched) {
      EPD47_touchp->getPoint(touch_xp, touch_yp, 0);
      EPD47_RotConvert(&touch_xp, &touch_yp);
    }
    //Touch_Check(EPD47_RotConvert);
  }
}
#endif // USE_TOUCH_BUTTONS



/*********************************************************************************************\
 * Interface
\*********************************************************************************************/

bool Xdsp16(uint8_t function)
{
  bool result = false;

  if (FUNC_DISPLAY_INIT_DRIVER == function) {
    EpdInitDriver47();
  }
  else if (epd47_init_done && (XDSP_16 == Settings->display_model)) {
    switch (function) {
      case FUNC_DISPLAY_MODEL:
        result = true;
        break;
#ifdef USE_TOUCH_BUTTONS
      case FUNC_DISPLAY_EVERY_50_MSECOND:
        if (FT5206_found) {
          EPD47_CheckTouch();
        }
        break;
#endif // USE_TOUCH_BUTTONS
    }
  }
  return result;
}

#endif  // USE_LILYGO47
#endif  // USE_DISPLAY
#endif  // ESP32