/*
 * Copyright (c) 2019, Erich Styger
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 *****************************************
 * Idea and image files from Adafruit:
 * https://learn.adafruit.com/animated-flying-toaster-oled-jewelry
 * Animated pendant for Adafruit Pro Trinket and SSD1306 OLED display,
 * inspired by the After Dark "Flying Toasters" screensaver.
 */

#include "platform.h"
#if PL_CONFIG_USE_TOASTER
#include "toaster.h"
#include <stdint.h>
#include <stdlib.h>
#include "McuUtility.h"
#include "McuArmTools.h"
#include "lv.h"
#include "McuSystemView.h"
#include "McuGDisplaySSD1306.h"

static const uint8_t
 toastermask0[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
  0x00, 0x20, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00,
  0x00, 0x10, 0x00, 0x00, 0x00, 0x43, 0xF8, 0x00,
  0x00, 0x1F, 0x80, 0x00, 0x00, 0x7C, 0x3F, 0x80,
  0x01, 0xF1, 0xF8, 0xF0, 0x07, 0xC7, 0xC7, 0x00,
  0x0F, 0x1F, 0x08, 0x2B, 0x1E, 0x7C, 0x11, 0x00,
  0x04, 0xF0, 0x20, 0x56, 0x61, 0xE4, 0x22, 0x00,
  0x78, 0x48, 0x40, 0xA8, 0x4E, 0x10, 0x44, 0x00,
  0x53, 0x90, 0x81, 0xF8, 0x5C, 0xA0, 0x90, 0x58,
  0x7C, 0xA1, 0x07, 0x90, 0x74, 0xA1, 0x21, 0x38,
  0x7F, 0xB5, 0x0E, 0x30, 0x77, 0xB0, 0x90, 0x78,
  0x7F, 0xB4, 0x60, 0xF0, 0x77, 0xBB, 0x03, 0xE0,
  0x7F, 0xBC, 0x0F, 0xC0, 0x77, 0xBF, 0xFF, 0x00,
  0x7F, 0xBF, 0xFC, 0x00, 0x3F, 0xBF, 0xF0, 0x00,
  0x1F, 0xBF, 0xC0, 0x00, 0x07, 0xBE, 0x00, 0x00,
  0x01, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
 toastermask1[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0xF8, 0x00,
  0x00, 0x1F, 0x80, 0x00, 0x00, 0x7C, 0x3F, 0x80,
  0x01, 0xF1, 0xF8, 0x00, 0x07, 0xC7, 0xC1, 0xE0,
  0x0F, 0x1F, 0x06, 0x10, 0x1E, 0x7C, 0x00, 0x00,
  0x04, 0xF0, 0x07, 0xF0, 0x61, 0xE4, 0x18, 0x08,
  0x78, 0x48, 0x20, 0x80, 0x4E, 0x10, 0x40, 0x2B,
  0x53, 0x90, 0x81, 0x00, 0x5C, 0xA0, 0x80, 0x58,
  0x7C, 0xA1, 0x02, 0x00, 0x74, 0xA1, 0x28, 0xB8,
  0x7F, 0xB5, 0x0F, 0xF0, 0x77, 0xB0, 0x90, 0x78,
  0x7F, 0xB4, 0x60, 0xF0, 0x77, 0xBB, 0x03, 0xE0,
  0x7F, 0xBC, 0x0F, 0xC0, 0x77, 0xBF, 0xFF, 0x00,
  0x7F, 0xBF, 0xFC, 0x00, 0x3F, 0xBF, 0xF0, 0x00,
  0x1F, 0xBF, 0xC0, 0x00, 0x07, 0xBE, 0x00, 0x00,
  0x01, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
 toastermask2[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF8, 0x00,
  0x00, 0x1F, 0x80, 0x00, 0x00, 0x7C, 0x3F, 0x80,
  0x01, 0xF1, 0xF8, 0x00, 0x07, 0xC7, 0xC1, 0xE0,
  0x0F, 0x1F, 0x06, 0x10, 0x1E, 0x7C, 0x00, 0x00,
  0x04, 0xF0, 0x00, 0x00, 0x61, 0xE4, 0x00, 0x08,
  0x78, 0x48, 0x00, 0x10, 0x4E, 0x10, 0x00, 0x18,
  0x53, 0x90, 0x60, 0x70, 0x5C, 0xA0, 0x9F, 0xC8,
  0x7C, 0xA1, 0x04, 0x92, 0x74, 0xA1, 0x09, 0x24,
  0x7F, 0xB5, 0x02, 0x48, 0x77, 0xB0, 0x80, 0x10,
  0x7F, 0xB4, 0x41, 0x00, 0x77, 0xBB, 0x20, 0x40,
  0x7F, 0xBC, 0x10, 0x00, 0x77, 0xBF, 0xF8, 0x00,
  0x7F, 0xBF, 0xFC, 0x00, 0x3F, 0xBF, 0xF0, 0x00,
  0x1F, 0xBF, 0xC0, 0x00, 0x07, 0xBE, 0x00, 0x00,
  0x01, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
 toastmask[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00,
  0x00, 0x0C, 0x30, 0x00, 0x00, 0x30, 0x0C, 0x00,
  0x00, 0xC2, 0x23, 0x00, 0x07, 0x00, 0x80, 0xC0,
  0x08, 0x25, 0x50, 0x30, 0x10, 0x0B, 0xA8, 0x08,
  0x21, 0x37, 0xF5, 0x04, 0x30, 0x4A, 0xE8, 0x0C,
  0x3C, 0x15, 0x52, 0x34, 0x2B, 0x00, 0x80, 0xEC,
  0x35, 0xC2, 0x23, 0x54, 0x1A, 0xB0, 0x0E, 0xAC,
  0x0D, 0x5C, 0x15, 0x58, 0x03, 0xAB, 0xEA, 0xE0,
  0x00, 0xD5, 0x55, 0x80, 0x00, 0x3A, 0xAE, 0x00,
  0x00, 0x0D, 0x58, 0x00, 0x00, 0x03, 0xE0, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
 toaster0[] = {
  0x00, 0x30, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00,
  0x01, 0xD8, 0x00, 0x00, 0x03, 0x74, 0xF0, 0x00,
  0x07, 0xEF, 0xFE, 0x00, 0x07, 0xBC, 0x07, 0x80,
  0x0F, 0xE0, 0x7F, 0xE0, 0x0F, 0x83, 0xC0, 0x70,
  0x1E, 0x0E, 0x07, 0x08, 0x18, 0x38, 0x38, 0xFF,
  0x30, 0xE0, 0xF7, 0xD4, 0x61, 0x83, 0xEE, 0xFF,
  0x7B, 0x0F, 0xDF, 0xA8, 0x9E, 0x1B, 0xDD, 0xFE,
  0x87, 0xB7, 0xBF, 0x50, 0xB1, 0xEF, 0xBB, 0xF8,
  0xAC, 0x6F, 0x7E, 0x00, 0xA3, 0x5F, 0x6F, 0xA0,
  0x83, 0x5E, 0xF8, 0x68, 0x8B, 0x5E, 0xDE, 0xC0,
  0x80, 0x4A, 0xF1, 0xC8, 0x88, 0x4F, 0x6F, 0x80,
  0x80, 0x4B, 0x9F, 0x08, 0x88, 0x44, 0xFC, 0x10,
  0x80, 0x43, 0xF0, 0x20, 0x88, 0x40, 0x00, 0xC0,
  0x80, 0x40, 0x03, 0x00, 0x40, 0x40, 0x0C, 0x00,
  0x20, 0x40, 0x30, 0x00, 0x18, 0x41, 0xC0, 0x00,
  0x06, 0x4E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00 },
 toaster1[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xF0, 0x00,
  0x00, 0xF7, 0xFE, 0x00, 0x01, 0xBC, 0x07, 0x80,
  0x03, 0xE0, 0x7F, 0xE0, 0x07, 0x83, 0xC0, 0x70,
  0x0E, 0x0E, 0x07, 0xF8, 0x18, 0x38, 0x3E, 0x18,
  0x30, 0xE0, 0xF9, 0xE8, 0x61, 0x83, 0xFF, 0xF8,
  0x7B, 0x0F, 0xF8, 0x08, 0x9E, 0x1B, 0xE7, 0xF0,
  0x87, 0xB7, 0xDF, 0x7F, 0xB1, 0xEF, 0xBF, 0xD4,
  0xAC, 0x6F, 0x7E, 0xFF, 0xA3, 0x5F, 0x7F, 0xA6,
  0x83, 0x5E, 0xFD, 0xF8, 0x8B, 0x5E, 0xD7, 0x40,
  0x80, 0x4A, 0xF0, 0x08, 0x88, 0x4F, 0x6F, 0x80,
  0x80, 0x4B, 0x9F, 0x08, 0x88, 0x44, 0xFC, 0x10,
  0x80, 0x43, 0xF0, 0x20, 0x88, 0x40, 0x00, 0xC0,
  0x80, 0x40, 0x03, 0x00, 0x40, 0x40, 0x0C, 0x00,
  0x20, 0x40, 0x30, 0x00, 0x18, 0x41, 0xC0, 0x00,
  0x06, 0x4E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00 },
 toaster2[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00,
  0x00, 0x07, 0xFE, 0x00, 0x00, 0x3C, 0x07, 0x80,
  0x00, 0xE0, 0x7F, 0xE0, 0x03, 0x83, 0xC0, 0x70,
  0x0E, 0x0E, 0x07, 0xF8, 0x18, 0x38, 0x3E, 0x18,
  0x30, 0xE0, 0xF9, 0xE8, 0x61, 0x83, 0xFF, 0xF8,
  0x7B, 0x0F, 0xFF, 0xF8, 0x9E, 0x1B, 0xFF, 0xF0,
  0x87, 0xB7, 0xFF, 0xE8, 0xB1, 0xEF, 0xFF, 0xE0,
  0xAC, 0x6F, 0x9F, 0x88, 0xA3, 0x5F, 0x60, 0x36,
  0x83, 0x5E, 0xFB, 0x6D, 0x8B, 0x5E, 0xF6, 0xDB,
  0x80, 0x4A, 0xFD, 0xB6, 0x88, 0x4F, 0x7F, 0xEE,
  0x80, 0x4B, 0xBE, 0xFC, 0x88, 0x44, 0xDF, 0xBC,
  0x80, 0x43, 0xEF, 0xF8, 0x88, 0x40, 0x07, 0xF0,
  0x80, 0x40, 0x03, 0xE0, 0x40, 0x40, 0x0C, 0xC0,
  0x20, 0x40, 0x30, 0x00, 0x18, 0x41, 0xC0, 0x00,
  0x06, 0x4E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00 },
 toast[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x03, 0xC0, 0x00, 0x00, 0x0F, 0xF0, 0x00,
  0x00, 0x3D, 0xDC, 0x00, 0x00, 0xFF, 0x7F, 0x00,
  0x07, 0xDA, 0xAF, 0xC0, 0x0F, 0xF4, 0x57, 0xF0,
  0x1E, 0xC8, 0x0A, 0xF8, 0x0F, 0xB5, 0x17, 0xF0,
  0x03, 0xEA, 0xAD, 0xC8, 0x14, 0xFF, 0x7F, 0x10,
  0x0A, 0x3D, 0xDC, 0xA8, 0x05, 0x4F, 0xF1, 0x50,
  0x02, 0xA3, 0xEA, 0xA0, 0x00, 0x54, 0x15, 0x00,
  0x00, 0x2A, 0xAA, 0x00, 0x00, 0x05, 0x50, 0x00,
  0x00, 0x02, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

static const uint8_t* const mask[] = {
  toastermask0, toastermask1, toastermask2, toastermask1, toastmask };
static const uint8_t* const img[]  = {
  toaster0    , toaster1    , toaster2    , toaster1    , toast     };

#define N_FLYERS   3 // Number of flying things

struct Flyer {       // Array of flying things
  int16_t x, y;      // Top-left position * 16 (for subpixel pos updates)
  int8_t  depth;     // Stacking order is also speed, 12-24 subpixels/frame
  uint8_t frame;     // Animation frame; Toasters cycle 0-3, Toast=255
} flyer[N_FLYERS];

// Flyer depth comparison function for qsort()
static int compare(const void *a, const void *b) {
  return ((struct Flyer *)a)->depth - ((struct Flyer *)b)->depth;
}

static void drawBitmap(McuGDisplaySSD1306_PixelDim x, McuGDisplaySSD1306_PixelDim y, const uint8_t *bitmap, McuGDisplaySSD1306_PixelDim w, McuGDisplaySSD1306_PixelDim h, McuGDisplaySSD1306_PixelColor color) {
  TIMAGE image;

  image.name = "";
  image.width = w;
  image.height = h;
  image.size = w*h;
  image.pixmap = bitmap;
  McuGDisplaySSD1306_DrawMonoBitmapMask(x, y, &image, color);
}

void TOASTER_DisplayToasters(void) {
  uint8_t i, f;
  int16_t x, y;
  bool resort = false;     // By default, don't re-sort depths

  McuGDisplaySSD1306_UpdateFull();
  McuGDisplaySSD1306_Clear(); /* screen black */

  for(i=0; i<N_FLYERS; i++) { // For each flyer...
    // First draw each item...
    f = (flyer[i].frame == 255) ? 4 : (flyer[i].frame++ & 3); // Frame #
    x = flyer[i].x / 16;
    y = flyer[i].y / 16;
    drawBitmap(x, y, mask[f], 32, 32, McuGDisplaySSD1306_COLOR_BLACK);
    drawBitmap(x, y, img[ f], 32, 32, McuGDisplaySSD1306_COLOR_WHITE);

    // Then update position, checking if item moved off screen...
    flyer[i].x -= flyer[i].depth * 2; // Update position based on depth,
    flyer[i].y += flyer[i].depth;     // for a sort of pseudo-parallax effect.
    if((flyer[i].y >= (64*16)) || (flyer[i].x <= (-32*16))) { // Off screen?
      if(McuUtility_random(0, 7-1) < 5) {         // Pick random edge; 0-4 = top
        flyer[i].x = McuUtility_random(0, 160-1) * 16;
        flyer[i].y = -32         * 16;
      } else {                    // 5-6 = right
        flyer[i].x = 128         * 16;
        flyer[i].y = McuUtility_random(0, 64-1)  * 16;
      }
      flyer[i].frame = McuUtility_random(0, 3-1) ? McuUtility_random(0, 4-1) : 255; // 66% toaster, else toast
      flyer[i].depth = 10 + McuUtility_random(0, 16-1);
      resort = true;
    }
  }
  // If any items were 'rebooted' to new position, re-sort all depths
  if(resort) {
    qsort(flyer, N_FLYERS, sizeof(struct Flyer), compare);
  }
}

//#define REFR_TIME_MS   (150) /* with I2C bit banging, it requires about 130 ms for a OLED refresh */
#define REFR_TIME_MS   (500) /* with I2C bit banging, it requires about 130 ms for a OLED refresh */
static lv_task_t *refr_task;
static lv_obj_t *win;
static bool screenSaverIsRunning = false;
static bool stopScreenSaver = false;

bool TOASTER_IsRunning(void) {
  return screenSaverIsRunning;
}

void TOASTER_StopScreenSaver(void) {
  stopScreenSaver = true;
}

static void refresh_task(struct _lv_task_t *param) {
  (void)param; /* not used */
  if (stopScreenSaver) {
    lv_obj_del(win);
    win = NULL;
    lv_task_del(refr_task);
    refr_task = NULL;
    screenSaverIsRunning = false;
  } else {
    screenSaverIsRunning = true;
#if 1
    TOASTER_DisplayToasters();
#elif 1
    static uint32_t cntr = 0;
    McuGDisplaySSD1306_PixelColor color;

    cntr++;
    if (cntr<20) {
      color = McuGDisplaySSD1306_COLOR_BLACK;
    } else if (cntr<40) {
      color = McuGDisplaySSD1306_COLOR_WHITE;
    } else {
      color = McuGDisplaySSD1306_COLOR_BLACK;
      cntr = 0;
    }
    McuGDisplaySSD1306_DrawLine(
        McuUtility_random(0, LV_HOR_RES-1), McuUtility_random(0, LV_VER_RES-1),
        McuUtility_random(0, LV_HOR_RES-1), McuUtility_random(0, LV_VER_RES-1),
        color);
    McuGDisplaySSD1306_UpdateFull();
#else
    line_points[0].x = McuUtility_random(0, 64);
    line_points[0].y = McuUtility_random(0, 64);
    line_points[1].x = McuUtility_random(0, 64);
    line_points[1].y = McuUtility_random(0, 64);
    lv_line_set_points(line1, line_points, 2);     /*Set the points*/
    lv_obj_align(line1, NULL, LV_ALIGN_IN_TOP_MID, 0, 20);
#endif
  }
}

void TOASTER_Show(void) {
  stopScreenSaver = false;
  screenSaverIsRunning = false;
  refr_task = lv_task_create(refresh_task, REFR_TIME_MS, LV_TASK_PRIO_LOW, NULL);

  win = lv_obj_create(lv_scr_act(), NULL);
  lv_obj_set_style(win, &lv_style_transp);
  lv_obj_set_size(win, LV_HOR_RES, LV_VER_RES);
}

void TOASTER_Init(void) {
  McuUtility_randomSetSeed(McuArmTools_GetCycleCounter());
  for(uint8_t i=0; i<N_FLYERS; i++) {  // Randomize initial flyer states
    flyer[i].x     = (-32 + McuUtility_random(0, 160-1)) * 16;
    flyer[i].y     = (-32 + McuUtility_random(0, 96-1)) * 16;
    flyer[i].frame = McuUtility_random(0, 3-1) ? McuUtility_random(0, 4-1) : 255; // 66% toaster, else toast
    flyer[i].depth = 10 + McuUtility_random(0, 16-1);             // Speed / stacking order
  }
  qsort(flyer, N_FLYERS, sizeof(struct Flyer), compare); // Sort depths
}

void TOASTER_Deinit(void) {}

#endif /* PL_CONFIG_USE_TOASTER */
