#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Adafruit_GFX.h>

#if defined(EPD)
#include <GxEPD2.h>
#else
#include <Adafruit_ILI9341.h>
#define TFT_DC 15
#define TFT_CS 22
#define BLACK ILI9341_BLACK
#define WHITE ILI9341_WHITE
Adafruit_ILI9341 display = Adafruit_ILI9341(TFT_CS, TFT_DC);
#endif

// Conversion factor for seconds to microseconds
#define S_TO_uS_FACTOR (1000000)
#define M_TO_uS_FACTOR (6e7)
#define H_TO_uS_FACTOR (3.6e9)
#define PCF_INT 35

const char* ssid = "Wokwi-GUEST";
const char* password = "";

const String url = "https://storage.googleapis.com/test-bucket-a83n/bar.bmp";
const String timeUrl = "http://worldtimeapi.org/api/timezone/Pacific/Auckland";

// Number of milliseconds to wait without receiving any data before we give up
const int kNetworkTimeout = 30*1000;
// Number of milliseconds to wait if no data is available before trying again
const int kNetworkDelay = 1000;

struct imageData {
    uint8_t* img;
    int len;
    int index;
};

uint16_t img_read16(struct imageData &image) {
  uint16_t result;
  uint8_t* img = image.img;
  ((uint8_t *)&result)[0] = *(img + image.index); // LSB
	((uint8_t *)&result)[1] = *(img + image.index + 1); // MSB
  image.index += 2;
	return result;
}

uint32_t img_read32(struct imageData &image) {
  uint32_t result;
  uint8_t* img = image.img;
  ((uint8_t *)&result)[0] = *(img + image.index); // LSB
	((uint8_t *)&result)[1] = *(img + image.index + 1);
	((uint8_t *)&result)[2] = *(img + image.index + 2);
	((uint8_t *)&result)[3] = *(img + image.index + 3); // MSB
  image.index += 4;
	return result;
}

uint8_t img_read(struct imageData &image) {
  uint8_t result;
  uint8_t* img = image.img;
  result = *(img + image.index);
  image.index += 1;
	return result;
}

static const uint16_t input_buffer_pixels = 20;  
static uint8_t input_buffer[3 * input_buffer_pixels];
static const uint16_t max_palette_pixels = 256;                 // for depth <= 8
static uint8_t mono_palette_buffer[max_palette_pixels / 8];     // palette buffer for depth <= 8 b/w

void display_bitmap(struct imageData &image)
{
	bool valid = false;     // valid format to be handled
	bool flip = true;       // bitmap is stored bottom-to-top
	uint32_t startTime = millis();

	// Parse BMP header
	if (img_read16(image) == 0x4D42) { // BMP signature
		uint32_t fileSize = img_read32(image);
		uint32_t creatorBytes = img_read32(image);
		uint32_t imageOffset = img_read32(image); // Start of image data
		uint32_t headerSize = img_read32(image);
		uint32_t width = img_read32(image);
		uint32_t height = img_read32(image);
		uint16_t planes = img_read16(image);
		uint16_t depth = img_read16(image);                                  // bits per pixel
		uint32_t format = img_read32(image);
		if ((planes == 1) && ((format == 0) || (format == 3))) {        // uncompressed is handled, 565 also
			// BMP rows are padded (if needed) to 4-byte boundary
			uint32_t rowSize = (width * depth / 8 + 3) & ~3;
			if (depth < 8) rowSize = ((width * depth + 8 - depth) / 8 + 3) & ~3;
			if (height < 0) {
				height = -height;
				flip = false;
			}
			uint16_t w = width;
			uint16_t h = height;
			valid = true;
			uint8_t bitmask = 0xFF;
			uint8_t bitshift = 8 - depth;
			uint16_t red, green, blue;
			bool whitish, colored;
			if (depth <= 8) {
				if (depth < 8) bitmask >>= depth;
				image.index = 54; //palette is always @ 54
				for (uint16_t pn = 0; pn < (1 << depth); pn++) {
					blue = img_read(image);
					green = img_read(image);
					red = img_read(image);
					img_read(image);
					whitish = ((red + green + blue) > 3 * 0x80);   // whitish
					if (0 == pn % 8) mono_palette_buffer[pn / 8] = 0;
					mono_palette_buffer[pn / 8] |= whitish << pn % 8;
				}
			}
			//display.fillScreen(WHITE);
			uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset;
			for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) { // for each line
				uint32_t in_remain = rowSize;
				uint32_t in_idx = 0;
				uint32_t in_bytes = 0;
				uint8_t in_byte = 0;    // for depth <= 8
				uint8_t in_bits = 0;    // for depth <= 8
				uint16_t color = BLACK;
				image.index = rowPosition;
				for (uint16_t col = 0; col < w; col++) {        // for each pixel
					                                        // Time to read more pixel data?
					if (in_idx >= in_bytes) {               // ok, exact match for 24bit also (size IS multiple of 3)
            in_bytes = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain;
            for (int i = 0; i < in_bytes; i++) {
                input_buffer[i] = img_read(image);
            }
						in_remain -= in_bytes;
						in_idx = 0;
					}
					switch (depth) {
					case 24:
						blue = input_buffer[in_idx++];
						green = input_buffer[in_idx++];
						red = input_buffer[in_idx++];
						whitish = ((red + green + blue) > 3 * 0x80);   // whitish
						colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0));                                                    // reddish or yellowish?
						break;
					case 16:
					{
						uint8_t lsb = input_buffer[in_idx++];
						uint8_t msb = input_buffer[in_idx++];
						if (format == 0) { // 555
							blue = (lsb & 0x1F) << 3;
							green = ((msb & 0x03) << 6) | ((lsb & 0xE0) >> 2);
							red = (msb & 0x7C) << 1;
						} else {// 565
							blue = (lsb & 0x1F) << 3;
							green = ((msb & 0x07) << 5) | ((lsb & 0xE0) >> 3);
							red = (msb & 0xF8);
						}
						whitish = ((red + green + blue) > 3 * 0x80);   // whitish
						colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0));                                                    // reddish or yellowish?
					}
					break;
					case 1:
					case 4:
					case 8:
					{
						if (0 == in_bits) {
							in_byte = input_buffer[in_idx++];
							in_bits = 8;
						}
						uint16_t pn = (in_byte >> bitshift) & bitmask;
						whitish = mono_palette_buffer[pn / 8] & (0x1 << pn % 8);
						in_byte <<= depth;
						in_bits -= depth;
					}
					break;
					}
					if (whitish)
						color = WHITE;
					else
						color = BLACK;
					uint16_t yrow = (flip ? h - row - 1 : row);
          if (col < display.width() && yrow < display.height()) {
					  display.drawPixel(col, yrow, color);
          }
				}       // end pixel
			}               // end line
		}
	}
}

String get_time() {
  HTTPClient http;
  http.useHTTP10(true);
  http.begin(timeUrl);
  http.GET();
  String result = http.getString();

  DynamicJsonDocument doc(2048);
  DeserializationError error = deserializeJson(doc, result);

  // Test if parsing succeeds.
  if (error) {
    Serial.print("deserializeJson() failed: ");
    Serial.println(error.c_str());
    return "<error>";
  }

  String datetime = doc["datetime"].as<String>();
  http.end();
  return datetime;
}


imageData download_image() {
    String dataStr = "";
    HTTPClient http;
    http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
    // configure server and url
    http.begin(url);
    // start connection and send HTTP header
    int httpCode = http.GET();
    if(httpCode < 1) {
				// TODO: Better error display
        dataStr = "HTTP GET failed with error code " + String(http.errorToString(httpCode).c_str()) + " \n";
        while(1);
    }

    if(httpCode != HTTP_CODE_OK) {
				// TODO: Better error display
        dataStr = "Server returned " + String(httpCode) + " error code\n";
        while(1);
    }

    // get length of document (is -1 when Server sends no Content-Length header)
    int len = http.getSize();
    int orig = len;

    // create buffer for read
    uint8_t buff[128] = { 0 };
    uint8_t * img = static_cast<uint8_t *>(malloc(len));

    // get tcp stream
    WiFiClient * stream = http.getStreamPtr();

    // read all data from server
    while(http.connected() && (len > 0 || len == -1)) {
        // get available data size
        size_t size = stream->available();

        if(size) {
            // read up to 128 byte
            int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));

            if(len > 0) {
                for (int i = 0; i < c; i++) {
                    img[i + (orig - len)] = buff[i];
                }
                len -= c;
            }
        }
        delay(1);
    }

    imageData image;
    image.img = img;
    image.len = orig;
    image.index = 0;

    return image;
}

int8_t deep_sleep_timer_wakeup(uint64_t sleep_time_us)
{
	// Turn off everything
	// disable_everything();

	esp_sleep_enable_timer_wakeup(sleep_time_us);

	// Go to sleep
	esp_deep_sleep_start();

	return 0;
}

void setup() {
		pinMode(PCF_INT, INPUT);
    WiFi.begin(ssid, password, 6);

		#if !defined(EPD)
		display.begin();
		#endif
		display.setRotation(1);

		while (WiFi.status() != WL_CONNECTED) {
      delay(100);
  	}

    imageData image = download_image();

    display_bitmap(image);

    free(image.img);

		display.setCursor(10, 50);
    display.setTextColor(BLACK);
		display.println(get_time());

    deep_sleep_timer_wakeup(20 * M_TO_uS_FACTOR);
}

void loop() {
}