/**
  This is a simple SAF frontend for Simon Tatham's Portable Puzzle Collection.
  Not all games work, the frontend is KISS and not 100% compliant, some games
  can't bear the low resolution.

  At start input is in normal mode, C button toggles mouse emulation. If mouse
  emulation is active, going completely to the righ brings up menu with
  options to start new game, undo, solve, write digits etc.

  how to:

  1. clone the STPPC
  2. copy this file and saf.h to that directory
  3. compile specific games e.g. as:
       gcc -O3 -DSAF_PLATFORM_SDL2 -DGAME_NAME=\"game.c\" \
         -lm -lSDL2 -o game stppc.c

  game stats:

  - workikng (fully or with very tiny glitches): 
      cube, untangle, fifteen, pegs, unequal, samegame, unruly, lightup,
      netslide, mosaic, singles, palisade

  - kinda working (minor glitches but playable):
      twiddle, dominosa, sixteen, pearl, towers, rect, range, tents, galaxies,
      inertia, undead

  - not working (unplayable):
      blackbox, bridges, map, mines, filling, flip (icons too small to see),
      net (too tiny), solo, slant (too tiny), tracks (divide by zero,
      possible bug due to small screen?), keen, loopy (too tiny),
      guess (not sure), magnets (almost works), signpost, pattern (tiny AF)

  This file is by drummyfish, released under CC0 (the puzzle collection itself
  is not included here and is available under different license).
*/

#define memswap _memswp // prevent name collision
#include "sort.c"
#undef memswap

#include "loopgen.h" // include everything!
#include "loopgen.c"
#include "penrose.h"
#include "penrose.c"
#include "grid.h"
#include "grid.c"
#include "tdq.c"
#include "combi.c"
#include "laydomino.c"
#include "findloop.c"
#include "tree234.c"
#include "latin.c"
#include "version.c"
#include "malloc.c"
#include "misc.c"
#include "drawing.c"
#include "printing.c"
#include "midend.c"
#include "random.c"
#include "matching.h"
#include "matching.c"
#include "dsf.c"
#include "divvy.c"
#include "puzzles.h"

#define encode_params _encp1 // prevent name collision
#define grid_type _gridt     // name collision in bridges.c
#include GAME_NAME

#define SAF_PROGRAM_NAME "STPPC " GAME_NAME

#define SAF_SETTING_ENABLE_SOUND 0
#define SAF_SETTING_ENABLE_SAVES 0

#include "saf.h"

#define MAX_COLORS 32
#define BAR_WIDTH 5
#define BAR_MAX_LENGTH 64
#define DEFAULT_COLOR SAF_COLOR_GRAY
#define GAME_MENU_ITEMS (10 + 3)

midend *middleEnd;
char barText[BAR_MAX_LENGTH + 1];
uint8_t colors[MAX_COLORS];
int colorCount;
uint8_t inputEmulation = 0;
uint8_t gameMenuItem = 0;
uint8_t cursorPos[2];
uint8_t cursorButtons[2];
int16_t clipRect[4];
int sw = SAF_SCREEN_WIDTH;
int sh = SAF_SCREEN_HEIGHT;

struct blitter
{
  uint8_t dummy;
};

struct blitter safBlitter;

char getGameMenuItem(int index)
{
  index %= GAME_MENU_ITEMS;

  switch (index)
  {
    case 0: return 'N'; break;
    case 1: return 'U'; break;
    case 2: return 'S'; break;

    default:
      return '0' + index - 4;
      break;
  }
}

void drawCursor(void)
{
  SAF_drawPixel(cursorPos[0],cursorPos[1],255);

  for (int i = 1; i <= 2; ++i)
  {
    SAF_drawPixel(cursorPos[0] - i,cursorPos[1],0);
    SAF_drawPixel(cursorPos[0] + i,cursorPos[1],0);
    SAF_drawPixel(cursorPos[0],cursorPos[1] - i,0);
    SAF_drawPixel(cursorPos[0],cursorPos[1] + i,0);
  }

  if (cursorPos[0] > SAF_SCREEN_WIDTH - BAR_WIDTH)
  {
    SAF_drawRect(SAF_SCREEN_WIDTH - BAR_WIDTH,0,BAR_WIDTH,
      SAF_SCREEN_HEIGHT,SAF_COLOR_WHITE,1);
      
    char mt[2];
 
    mt[1] = 0;

    for (int i = 0; i < SAF_SCREEN_HEIGHT / 5; ++i)
    {
      mt[0] = getGameMenuItem(gameMenuItem + i);

      SAF_drawText(mt,SAF_SCREEN_WIDTH - BAR_WIDTH + 1,1 + i * 5,
       (mt[0] >= 'A' && mt[0] <= 'Z') ? SAF_COLOR_BLUE : SAF_COLOR_BLACK,1);
    }

    SAF_drawRect(SAF_SCREEN_WIDTH - BAR_WIDTH,0,BAR_WIDTH,6,
      SAF_COLOR_GREEN_DARK,0);
  }
}

int pointNotClipped(int x, int y)
{
  return (x >= clipRect[0] && x < clipRect[2] &&
    y >= clipRect[1] && y < clipRect[3]);
}

void deactivate_timer(frontend *fe)
{
}

void activate_timer(frontend *fe)
{
}

void fatal(const char *fmt, ...)
{
}

void frontend_default_colour(frontend *fe, float *output)
{
  uint8_t r, g, b;

  SAF_colorToRGB(DEFAULT_COLOR,&r,&g,&b);

  output[0] = ((float) r) / 255.0;
  output[1] = ((float) g) / 255.0;
  output[2] = ((float) b) / 255.0;
}

void get_random_seed(void **randseed, int *randseedsize)
{
  *randseed = malloc(4);
  **((uint32_t **) randseed) = SAF_time();
  *randseedsize = 4;
}

static void saf_status_bar(void *handle, const char *text)
{
  int i = 0;

  while (*text != 0 && i < BAR_MAX_LENGTH)
  {
    barText[i] = *text;
    text++;
    i++;
  }

  barText[i] = 0;
}

static blitter *saf_blitter_new(void *handle, int w, int h)
{
  return &safBlitter;
}

static void saf_blitter_free(void *handle, blitter *bl)
{
}

static void saf_blitter_save(void *handle, blitter *bl, int x, int y)
{
}

static void saf_blitter_load(void *handle, blitter *bl, int x, int y)
{
}

static void saf_set_brush(frontend *fe, int colour)
{
}

static void saf_reset_brush(frontend *fe)
{
}

static void saf_set_pen(frontend *fe, int colour, int thin)
{
}

static void saf_reset_pen(frontend *fe)
{
}

static void saf_clip(void *handle, int x, int y, int w, int h)
{
  clipRect[0] = x;
  clipRect[1] = y;
  clipRect[2] = x + w;
  clipRect[3] = y + h;
}

static void saf_unclip(void *handle)
{
  clipRect[0] = -1000;
  clipRect[1] = -1000;
  clipRect[2] = 1000;
  clipRect[3] = 1000;
}

static void saf_draw_text(void *handle, int x, int y, int fonttype,
              int fontsize, int align, int colour, const char *text)
{
  int l = 0;
  const char *t = text;

  while (*t != 0)
  {
    l++;
    t++;
  }

  if (align & ALIGN_HCENTRE)
    x -= (l * 5) / 2;

  if (align & ALIGN_VCENTRE)
    y -= 2;

#define TOLERANCE 2

  if (pointNotClipped(x + TOLERANCE,y + TOLERANCE) &&
    pointNotClipped(x + l * 5 - TOLERANCE,y + 4 - TOLERANCE))
    SAF_drawText(text,x,y,colors[colour],1);
}

static void saf_draw_rect(void *handle, int x, int y, int w, int h, int colour)
{
  w += x;
  h += y;

  if (x < clipRect[0])
    x = clipRect[0];
  else if (x > clipRect[2])
    x = clipRect[2];

  if (y < clipRect[1])
    y = clipRect[1];
  else if (y > clipRect[3])
    y = clipRect[3];

  if (w < clipRect[0])
    w = clipRect[0];
  else if (w > clipRect[2])
    w = clipRect[2];

  if (h < clipRect[1])
    h = clipRect[1];
  else if (h > clipRect[3])
    h = clipRect[3];

  w -= x;
  h -= y;

  SAF_drawRect(x,y,w,h,colors[colour],1);
}

static void saf_draw_line(void *handle, int x1, int y1, int x2, int y2,
  int colour)
{
  // TODO: points could actually be clipped and drawn
  if (pointNotClipped(x1,y1) && pointNotClipped(x2,y2))
    SAF_drawLine(x1,y1,x2,y2,colors[colour]);
}
    
void saf_draw_thick_line(void *handle, float thickness, float x1, float y1,
  float x2, float y2, int colour)
{
  if (pointNotClipped(x1,y1) && pointNotClipped(x2,y2))
    SAF_drawLine(x1,y1,x2,y2,colors[colour]);
}

static void saf_draw_circle(void *handle, int cx, int cy, int radius,
                int fillcolour, int outlinecolour)
{
  if (pointNotClipped(cx,cy))
  {
    if (fillcolour >= 0)
      SAF_drawCircle(cx,cy,radius,colors[fillcolour],1);
  
    SAF_drawCircle(cx,cy,radius,colors[outlinecolour],0);
  }
}

int pointIsInTriangle(int px, int py, int tp[6])
{
  // winding of the whole triangle:
  int w = (tp[3] - tp[1]) * (tp[4] - tp[2]) - (tp[2] - tp[0]) * (tp[5] - tp[3]);
  int sign = w > 0 ? 1 : (w < 0 ? -1 : 0);

  for (int i = 0; i < 3; ++i) // test winding of point with each side
  {
    int i1 = 2 * i;
    int i2 = i1 != 4 ? i1 + 2 : 0;

    int w2 = (tp[i1 + 1] - py) * (tp[i2] - tp[i1]) - (tp[i1] - px) * (tp[i2 + 1] - tp[i1 + 1]);
    int sign2 = w2 > 0 ? 1 : (w2 < 0 ? -1 : 0);
      
    if (sign * sign2 == -1) // includes edges
    //if (sign != sign2) // excludes edges
      return 0;
  }
    
  return 1;
}

static void saf_draw_polygon(void *handle, const int *coords, int npoints,
  int fillcolour, int outlinecolour)
{
  if (fillcolour >= 0)
  {
    /* filling a polygon is hard, this method is imperfect, it just draws
       triangles in hopes it will mostly work :) */

    int cx = 0, cy = 0;
    int minX = clipRect[2],
        minY = clipRect[3],
        maxX = clipRect[0],
        maxY = clipRect[1];

    for (int i = 0; i < npoints; ++i)
    {
      cx += coords[2 * i];
      cy += coords[2 * i + 1];

      if (coords[2 * i] > maxX)
        maxX = coords[2 * i];

      if (coords[2 * i] < minX)
        minX = coords[2 * i];

      if (coords[2 * i + 1] > maxY)
        maxY = coords[2 * i + 1];

      if (coords[2 * i + 1] < minY)
        minY = coords[2 * i + 1];
    }

    cx /= npoints;
    cy /= npoints;

    for (int i = 0; i < npoints; ++i)
    {
      int tp[6];
      tp[0] = cx; tp[1] = cy;
      tp[2] = coords[2 * i]; tp[3] = coords[2 * i + 1];
      tp[4] = coords[2 * ((i + 1) % npoints)], 
      tp[5] = coords[2 * ((i + 1) % npoints) + 1];

      if (pointNotClipped(tp[2],tp[3]) &&
          pointNotClipped(tp[4],tp[5]))
        for (int y = minY; y < maxY; ++y)
          for (int x = minX; x < maxX; ++x)
            if (pointIsInTriangle(x,y,tp))
              SAF_drawPixel(x,y,colors[fillcolour]);
    }
  }

  for (int i = 0; i < npoints; ++i)
  {
    int i1 = 2 * i, i2 = 2 * ((i + 1) % npoints);

    if (pointNotClipped(coords[i],coords[i + 1]) &&
      pointNotClipped(coords[i2],coords[i2 + 1]))
      SAF_drawLine(  
        coords[i1],coords[i1 + 1],
        coords[i2],coords[i2 + 1],
        colors[outlinecolour]
    );
  }
}

static void saf_start_draw(void *handle)
{
  SAF_drawRect(sw,0,SAF_SCREEN_WIDTH - sw,SAF_SCREEN_HEIGHT,DEFAULT_COLOR,1);
  SAF_drawRect(0,sh,SAF_SCREEN_WIDTH,SAF_SCREEN_HEIGHT - sh,DEFAULT_COLOR,1);
}

static void saf_draw_update(void *handle, int x, int y, int w, int h)
{
}

static void saf_end_draw(void *handle)
{
}

static void saf_line_width(void *handle, float width)
{
}

static void saf_line_dotted(void *handle, bool dotted)
{
}

static void saf_begin_doc(void *handle, int pages)
{
}

static void saf_begin_page(void *handle, int number)
{
}

static void saf_begin_puzzle(void *handle, float xm, float xc,
                 float ym, float yc, int pw, int ph, float wmm)
{
}

static void saf_end_puzzle(void *handle)
{
}

static void saf_end_page(void *handle, int number)
{
}

static void saf_end_doc(void *handle)
{
}

char *saf_text_fallback(void *handle, const char *const *strings, int nstrings)
{
  return NULL;
}

const struct drawing_api drawAPI =
{
  saf_draw_text,
  saf_draw_rect,
  saf_draw_line,
  saf_draw_polygon,
  saf_draw_circle,
  saf_draw_update,
  saf_clip,
  saf_unclip,
  saf_start_draw,
  saf_end_draw,
  saf_status_bar,
  saf_blitter_new,
  saf_blitter_free,
  saf_blitter_save,
  saf_blitter_load,
  saf_begin_doc,
  saf_begin_page,
  saf_begin_puzzle,
  saf_end_puzzle,
  saf_end_page,
  saf_end_doc,
  saf_line_width,
  saf_line_dotted,
  saf_text_fallback,
  saf_draw_thick_line
};

void clearBar(void)
{
  for (int i = 0; i < BAR_MAX_LENGTH; ++i)
    barText[i] = ' ';

  barText[BAR_MAX_LENGTH] = 0;
}

void SAF_init(void)
{
  float *tmpColors;

  SAF_clearScreen(DEFAULT_COLOR);

  saf_unclip(NULL);

  clearBar();

  middleEnd = midend_new(NULL,&thegame,&drawAPI,NULL);
  tmpColors = midend_colours(middleEnd,&colorCount);
  colorCount %= MAX_COLORS;

  for (int i = 0; i < colorCount; ++i)
  {
    colors[i] = SAF_colorFromRGB( 
      tmpColors[3 * i] * 255,
      tmpColors[3 * i + 1] * 255,
      tmpColors[3 * i + 2] * 255);
  }

  midend_new_game(middleEnd);

  sw = SAF_SCREEN_WIDTH;
  sh = SAF_SCREEN_HEIGHT - BAR_WIDTH;

  midend_reset_tilesize(middleEnd);
  midend_size(middleEnd,&sw,&sh,1);

  cursorPos[0] = SAF_SCREEN_WIDTH / 2;
  cursorPos[1] = SAF_SCREEN_HEIGHT / 2;

  cursorButtons[0] = 0;
  cursorButtons[1] = 0;

  midend_which_preset(middleEnd);
  midend_redraw(middleEnd);
}

int buttonOn(uint8_t button)
{
  return SAF_buttonPressed(button) == 1 || SAF_buttonPressed(button) > 10;
}

uint8_t SAF_loop(void)
{
  midend_timer(middleEnd,1.0 / ((float) SAF_MS_PER_FRAME));

  if (inputEmulation)
  {
    if (cursorPos[0] <= SAF_SCREEN_WIDTH - BAR_WIDTH)
    {
      uint8_t redraw = 1;

      if (buttonOn(SAF_BUTTON_LEFT) && cursorPos[0] > 0)
      {
        cursorPos[0]--;

        if (cursorButtons[0])
          midend_process_key(middleEnd,cursorPos[0],cursorPos[1],LEFT_DRAG);
      }
      else if (buttonOn(SAF_BUTTON_RIGHT) && cursorPos[0] < SAF_SCREEN_WIDTH - 1)
      {
        cursorPos[0]++;

        if (cursorButtons[0])
          midend_process_key(middleEnd,cursorPos[0],cursorPos[1],LEFT_DRAG);
      }
      else if (buttonOn(SAF_BUTTON_UP) && cursorPos[1] > 0)
      {
        cursorPos[1]--;

        if (cursorButtons[0])
          midend_process_key(middleEnd,cursorPos[0],cursorPos[1],LEFT_DRAG);
      }
      else if (buttonOn(SAF_BUTTON_DOWN) && cursorPos[1] < SAF_SCREEN_HEIGHT - 1)
      {
        cursorPos[1]++;

        if (cursorButtons[0])
          midend_process_key(middleEnd,cursorPos[0],cursorPos[1],LEFT_DRAG);
      }
      else if ((SAF_buttonPressed(SAF_BUTTON_A) != 0) != cursorButtons[0])
      {
        cursorButtons[0] = SAF_buttonPressed(SAF_BUTTON_A) != 0;

        midend_process_key(middleEnd,cursorPos[0],cursorPos[1],
          cursorButtons[0] ? LEFT_BUTTON : LEFT_RELEASE);
      }
      else if ((SAF_buttonPressed(SAF_BUTTON_B) != 0) != cursorButtons[1])
      {
        cursorButtons[1] = SAF_buttonPressed(SAF_BUTTON_B) != 0;

        midend_process_key(middleEnd,cursorPos[0],cursorPos[1],
          cursorButtons[1] ? RIGHT_BUTTON : RIGHT_RELEASE);
      }
      else
        redraw = 0;

      if (redraw)
        midend_force_redraw(middleEnd);
    }
    else
    {
      if (buttonOn(SAF_BUTTON_LEFT))
        cursorPos[0]--;
      else if (buttonOn(SAF_BUTTON_UP))
        gameMenuItem = 
          gameMenuItem > 0 ? gameMenuItem - 1 : GAME_MENU_ITEMS - 1;
      else if (buttonOn(SAF_BUTTON_DOWN))
        gameMenuItem = 
          gameMenuItem < GAME_MENU_ITEMS - 1 ? gameMenuItem + 1 : 0;      
      else if (buttonOn(SAF_BUTTON_A))
      {
        char item = getGameMenuItem(gameMenuItem);

        switch (item)
        {
          case 'S':
            midend_solve(middleEnd);
            midend_force_redraw(middleEnd);
            break;
 
          case 'N':
            midend_new_game(middleEnd);
            midend_force_redraw(middleEnd);
            break;
 
          case 'U':
            midend_undo(middleEnd);
            midend_force_redraw(middleEnd);
            break;

          default:
            midend_process_key(middleEnd,cursorPos[0],cursorPos[1],item);
            break;
        }
      }
    }
  }
  else
  {
    if (SAF_buttonJustPressed(SAF_BUTTON_LEFT))
      midend_process_key(middleEnd,0,0,CURSOR_LEFT);

    if (SAF_buttonJustPressed(SAF_BUTTON_RIGHT))
      midend_process_key(middleEnd,0,0,CURSOR_RIGHT);

    if (SAF_buttonJustPressed(SAF_BUTTON_UP))
      midend_process_key(middleEnd,0,0,CURSOR_UP);

    if (SAF_buttonJustPressed(SAF_BUTTON_DOWN))
      midend_process_key(middleEnd,0,0,CURSOR_DOWN);

    if (SAF_buttonJustPressed(SAF_BUTTON_A))
      midend_process_key(middleEnd,0,0,CURSOR_SELECT);

    if (SAF_buttonJustPressed(SAF_BUTTON_B))
      midend_process_key(middleEnd,0,0,CURSOR_SELECT2);
  }

  if (SAF_buttonJustPressed(SAF_BUTTON_C))
  {
    inputEmulation = !inputEmulation;
    midend_force_redraw(middleEnd);
  }
    
  SAF_drawRect(0,SAF_SCREEN_HEIGHT - BAR_WIDTH,SAF_SCREEN_WIDTH,BAR_WIDTH,
    SAF_COLOR_WHITE,1);

  SAF_drawText(barText,1,SAF_SCREEN_HEIGHT - BAR_WIDTH + 1,SAF_COLOR_BLACK,1);

  int status = midend_status(middleEnd);

  if (status == 1)
    SAF_drawText("WIN",1,1,SAF_COLOR_GREEN,1);
  else if (status == -1)
    SAF_drawText("LOST",1,1,SAF_COLOR_RED,1);

  if (inputEmulation)
    drawCursor();

  return 1;
}
