#pragma once

#include "wled.h"
#include "FX.h"
#include "fcn_declare.h"

#include "tetrisaigame.h"
// By: muebau

typedef struct TetrisAI_data
{
  unsigned long lastTime = 0;
  TetrisAIGame tetris;
  uint8_t   intelligence;
  uint8_t   rotate;
  bool      showNext;
  bool      showBorder;
  uint8_t   colorOffset;
  uint8_t   colorInc;
  uint8_t   mistaceCountdown;
  uint16_t segcols;
  uint16_t segrows;
  uint16_t segOffsetX;
  uint16_t segOffsetY;
  uint16_t effectWidth;
  uint16_t effectHeight;
} tetrisai_data;

void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
{
  SEGMENT.fill(SEGCOLOR(1));

  //GRID
  for (auto index_y = 4; index_y < tetris->grid.height; index_y++)
  {
    for (auto index_x = 0; index_x < tetris->grid.width; index_x++)
    {
      CRGB color;
      if (*tetris->grid.getPixel(index_x, index_y) == 0)
      {
        //BG color
        color = SEGCOLOR(1);
      }
      //game over animation
      else if(*tetris->grid.getPixel(index_x, index_y) == 254)
      {
        //use fg
        color = SEGCOLOR(0);
      }
      else
      {
        //spread the color over the whole palette
        uint8_t colorIndex = *tetris->grid.getPixel(index_x, index_y) * 32;
        colorIndex += tetrisai_data->colorOffset;
        color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND);
      }

      SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + index_x, tetrisai_data->segOffsetY + index_y - 4, color);
    }
  }
  tetrisai_data->colorOffset += tetrisai_data->colorInc;

  //NEXT PIECE AREA
  if (tetrisai_data->showNext)
  {
    //BORDER
    if (tetrisai_data->showBorder)
    {
      //draw a line 6 pixels from right with the border color
      for (auto index_y = 0; index_y < tetrisai_data->effectHeight; index_y++)
      {
        SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + tetrisai_data->effectWidth - 6, tetrisai_data->segOffsetY + index_y, SEGCOLOR(2));
      }
    }

    //NEXT PIECE
    int piecesOffsetX = tetrisai_data->effectWidth - 4;
    int piecesOffsetY = 1;
    for (uint8_t nextPieceIdx = 1; nextPieceIdx < tetris->nLookAhead; nextPieceIdx++)
    {
      uint8_t pieceNbrOffsetY = (nextPieceIdx - 1) * 5;

      Piece piece(tetris->bag.piecesQueue[nextPieceIdx]);

      for (uint8_t pieceY = 0; pieceY < piece.getRotation().height; pieceY++)
      {
        for (uint8_t pieceX = 0; pieceX < piece.getRotation().width; pieceX++)
        {
          if (piece.getPixel(pieceX, pieceY))
          {
            uint8_t colIdx = ((piece.pieceData->colorIndex * 32) + tetrisai_data->colorOffset);
            SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + piecesOffsetX + pieceX, tetrisai_data->segOffsetY + piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND));
          }
        }
      }
    }
  }
}

////////////////////////////
//     2D Tetris AI       //
////////////////////////////
uint16_t mode_2DTetrisAI()
{
  if (!strip.isMatrix || !SEGENV.allocateData(sizeof(tetrisai_data)))
  {
    // not a 2D set-up
    SEGMENT.fill(SEGCOLOR(0));
    return 350;
  }
  TetrisAI_data* tetrisai_data = reinterpret_cast<TetrisAI_data*>(SEGENV.data);

  const uint16_t cols = SEGMENT.virtualWidth();
  const uint16_t rows = SEGMENT.virtualHeight();

  //range 0 - 1024ms => 1024/255 ~ 4
  uint16_t msDelayMove = 1024 - (4 * SEGMENT.speed);
  int16_t msDelayGameOver = msDelayMove / 4;

  //range 0 - 2 (not including current)
  uint8_t nLookAhead = SEGMENT.intensity ? (SEGMENT.intensity >> 7) + 2 : 1;
  //range 0 - 16
  tetrisai_data->colorInc = SEGMENT.custom2 >> 4;

  if (tetrisai_data->tetris.nLookAhead != nLookAhead
    || tetrisai_data->segcols != cols
    || tetrisai_data->segrows != rows
    || tetrisai_data->showNext != SEGMENT.check1
    || tetrisai_data->showBorder != SEGMENT.check2
  )
  {
    tetrisai_data->segcols = cols;
    tetrisai_data->segrows = rows;
    tetrisai_data->showNext = SEGMENT.check1;
    tetrisai_data->showBorder = SEGMENT.check2;

    //not more than 32 columns and 255 rows as this is the limit of this implementation
    uint8_t gridWidth = cols > 32 ? 32 : cols;
    uint8_t gridHeight = rows > 255 ? 255 : rows;

    tetrisai_data->effectWidth = 0;
    tetrisai_data->effectHeight = 0;

    // do we need space for the 'next' section?
    if (tetrisai_data->showNext)
    {
      //does it get to tight?
      if (gridWidth + 5 > cols)
      {
        // yes, so make the grid smaller
        // make space for the piece and one pixel of space
        gridWidth = (gridWidth - ((gridWidth + 5) - cols));
      }
      tetrisai_data->effectWidth += 5;

      // do we need space for a border?
      if (tetrisai_data->showBorder)
      {
        if (gridWidth + 5 + 1 > cols)
        {
          gridWidth -= 1;
        }
        tetrisai_data->effectWidth += 1;
      }
    }

    tetrisai_data->effectWidth += gridWidth;
    tetrisai_data->effectHeight += gridHeight;

    tetrisai_data->segOffsetX = cols > tetrisai_data->effectWidth ? ((cols - tetrisai_data->effectWidth) / 2) : 0;
    tetrisai_data->segOffsetY = rows > tetrisai_data->effectHeight ? ((rows - tetrisai_data->effectHeight) / 2) : 0;

    tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces);
    tetrisai_data->tetris.state = TetrisAIGame::States::INIT;
    SEGMENT.fill(SEGCOLOR(1));
  }

  if (tetrisai_data->intelligence != SEGMENT.custom1)
  {
    tetrisai_data->intelligence = SEGMENT.custom1;
    float dui = 0.2f - (0.2f * (tetrisai_data->intelligence / 255.0f));

    tetrisai_data->tetris.ai.aHeight = -0.510066f + dui;
    tetrisai_data->tetris.ai.fullLines = 0.760666f - dui;
    tetrisai_data->tetris.ai.holes = -0.35663f + dui;
    tetrisai_data->tetris.ai.bumpiness = -0.184483f + dui;
  }

  if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE)
  {
    
    if (strip.now - tetrisai_data->lastTime > msDelayMove)
    {
      drawGrid(&tetrisai_data->tetris, tetrisai_data);
      tetrisai_data->lastTime = strip.now;
      tetrisai_data->tetris.poll();
    }
  }
  else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_GAME_OVER)
  {
    if (strip.now - tetrisai_data->lastTime > msDelayGameOver)
    {
      drawGrid(&tetrisai_data->tetris, tetrisai_data);
      tetrisai_data->lastTime = strip.now;
      tetrisai_data->tetris.poll();
    }
  }
  else if (tetrisai_data->tetris.state == TetrisAIGame::FIND_BEST_MOVE)
  {
    if (SEGMENT.check3)
    {
      if(tetrisai_data->mistaceCountdown == 0)
      {
        tetrisai_data->tetris.ai.findWorstMove = true;
        tetrisai_data->tetris.poll();
        tetrisai_data->tetris.ai.findWorstMove = false;
        tetrisai_data->mistaceCountdown = SEGMENT.custom3;
      }
      tetrisai_data->mistaceCountdown--;      
    }
    tetrisai_data->tetris.poll();
  }
  else
  {
    tetrisai_data->tetris.poll();
  }

  return FRAMETIME;
} // mode_2DTetrisAI()
static const char _data_FX_MODE_2DTETRISAI[] PROGMEM = "Tetris AI@!,Look ahead,Intelligence,Rotate color,Mistake free,Show next,Border,Mistakes;Game Over,!,Border;!;2;sx=127,ix=64,c1=255,c2=0,c3=31,o1=1,o2=1,o3=0,pal=11";

class TetrisAIUsermod : public Usermod
{

private:

public:
  void setup()
  {
    strip.addEffect(255, &mode_2DTetrisAI, _data_FX_MODE_2DTETRISAI);
  }

  void loop()
  {

  }

  uint16_t getId()
  {
    return USERMOD_ID_TETRISAI;
  }
};
