/**
 * File: bootAnim.cpp
 *
 * Author: Al Chaussee
 * Created: 07/27/2018
 *
 * Description: Draws the boot animation to the screen forever until stopped
 *              Leaves the screen on when stopped under the assumption whatever is stopping
 *              it will continue to use the screen
 *
 * Copyright: Anki, Inc. 2018
 *
 **/

#include "core/lcd.h"

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <chrono>
#include <algorithm>
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>

#define FRAME_WIDTH_SANTEK  184 
#define FRAME_HEIGHT_SANTEK 96
#define FRAME_WIDTH_MIDAS 160
#define FRAME_HEIGHT_MIDAS 80
#define NUM_BYTES_PER_FRAME_SANTEK (FRAME_WIDTH_SANTEK * FRAME_HEIGHT_SANTEK * sizeof(uint16_t))
#define NUM_BYTES_PER_FRAME_MIDAS (FRAME_WIDTH_MIDAS * FRAME_HEIGHT_MIDAS * sizeof(uint16_t))

namespace {
  // Path to the boot animation file generated by `python3 /robot/test/gif_to_raw.py <gif of boot animation>`
  // It is a binary file containing all the frames in the animation in rgb565 format
  static const char* _animPathSantek = "/anki/data/assets/cozmo_resources/config/engine/animations/boot_anim.raw";
  static const char* _animPathMidas = "/anki/data/assets/cozmo_resources/config/engine/animations/boot_anim_20.raw";
  static const char* _animPathCustom = "/persist/boot_anim.raw";
  bool gShutdown = false;
}

extern "C" void core_common_on_exit(void)
{
  lcd_shutdown();
}

bool use_santek_sizes() {
  return lcd_display_version() == SANTEK;
}

static void animate(void* frame)
{
  if (use_santek_sizes()) {
    lcd_draw_frame2((uint16_t*)frame, FRAME_WIDTH_SANTEK * FRAME_HEIGHT_SANTEK * sizeof(uint16_t));
  } else {
    lcd_draw_frame2((uint16_t*)frame, FRAME_WIDTH_MIDAS * FRAME_HEIGHT_MIDAS * sizeof(uint16_t));
  };
}

static void handler(int signum)
{
  gShutdown = true;
}

bool custom_exists() {
  struct stat buffer;   
  return (stat("/persist/boot_anim.raw", &buffer) == 0);
}

int main(int argc, char** argv)
{
  // Setup signal handlers so we can cleanly exit
  signal(SIGTERM, handler);
  signal(SIGINT,  handler);

  lcd_init();
  lcd_clear_screen();
  lcd_shutdown();
  
  // Init lcd
  int rc = lcd_init();
  if (rc != 0)
  {
    printf("Failed to init lcd\n");
    return rc;
  }

  // Open animation file for reading
  const char *anim_path = use_santek_sizes() ? _animPathSantek : _animPathMidas;
  const char *true_anim_path = custom_exists() ? _animPathCustom : anim_path;
  
  int fd = open(true_anim_path, O_RDONLY, 0644);
  if(fd < 0)
  {
    printf("Failed to open %s\n", true_anim_path);
    return -1;
  }

  // Get the length of the file to determine how long the animation is
  off_t len = lseek(fd, 0, SEEK_END);
  if(len == -1)
  {
    printf("lseek failed\n");
    return -1;
  }
  static const uint32_t kTotalNumFrames = len / ( use_santek_sizes() ? NUM_BYTES_PER_FRAME_SANTEK : NUM_BYTES_PER_FRAME_MIDAS);

  // Memory map the anim file
  void* anim = mmap(nullptr, len, PROT_READ, MAP_PRIVATE, fd, 0);
  if(anim == nullptr)
  {
    printf("mmap failed\n");
    return -1;
  }

  // Close the fd after memory mapping
  rc = close(fd);
  if(rc != 0)
  {
    printf("close failed\n");
  }  

  // Start drawing the boot animation to the screen

  // Keep track of how far in time we are in this loop of the animation
  // since drawing the frame takes longer than the frame should actually
  // be displayed (~41ms to draw vs a desired frame rate of 24fps)
  // we need to skip frames
  uint32_t timeCount = 0;
  
  while (!gShutdown)
  {
    // Figure out which frame we should play in order to adhere to
    // a frame rate of kFrameDuration_ms
    static const uint32_t kFrameDuration_ms = 41;
    const uint32_t nextFrameToDraw = timeCount / kFrameDuration_ms;

    // Time how long it takes to animate/draw this frame
    const auto startTime = std::chrono::steady_clock::now();

    const uint32_t frame_offset = use_santek_sizes() ? NUM_BYTES_PER_FRAME_SANTEK : NUM_BYTES_PER_FRAME_MIDAS;
    
    animate(((uint8_t*)anim) + (nextFrameToDraw * frame_offset));
    const auto endTime = std::chrono::steady_clock::now();

    const auto dif = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
    timeCount += dif.count();

    // Role timeCount over if it exceeds the total duration of the animation
    if(timeCount >= ((kTotalNumFrames-1)*kFrameDuration_ms))
    {
      timeCount %= kTotalNumFrames * kFrameDuration_ms;
    }
  }

  // Unmap the anim
  rc = munmap(anim, len);
  if(rc != 0)
  {
    printf("munmap failed\n");
  }

  lcd_shutdown();
  return 0;
}

