/* pgnviewer.c -- the SDL2 window, event loop, and drawing for the
 * PGN viewer.
 *
 * Design: one game is shown at a time (see pgnlib.h). Left/Right
 * arrows step one ply at a time; Up/Down switch to the
 * previous/next game in the file; Home/End jump to the start/end of
 * the current game.
 */

#include <stdio.h>
#include <stdlib.h>

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

#include "pgnlib.h"
#include "sanvar.h"
#include "sanint.h"
#include "sanfen.h"

#define SQUARE_SIZE   48
#define BOARD_PIXELS  (SQUARE_SIZE * 10)
#define INFO_HEIGHT   40
#define WINDOW_W      BOARD_PIXELS
#define WINDOW_H      (BOARD_PIXELS + INFO_HEIGHT)

#define LIGHT_SQUARE_R 240
#define LIGHT_SQUARE_G 217
#define LIGHT_SQUARE_B 181

static TTF_Font *font;
static TTF_Font *chessfont;
static SDL_Renderer *renderer;

/*--> DrawGlyph: render one chessfont character, centered, into the
      square at (screenCol, screenRow) -- shared by DrawBorderElement()
      and the two-layer DrawPiece() below */
static void DrawGlyph (int screenCol, int screenRow, char c)
{
  char text[2];
  SDL_Color color;
  SDL_Surface *surf;
  SDL_Texture *tex;
  SDL_Rect dst;

  text[0] = c;
  text[1] = '\0';

  color.a = 255;
  color.r = 20;
  color.g = 20;
  color.b = 20;
  surf = TTF_RenderText_Blended (chessfont, text, color);

  if (surf == NULL)
    return;

  tex = SDL_CreateTextureFromSurface (renderer, surf);
  dst.x = screenCol * SQUARE_SIZE + (SQUARE_SIZE - surf->w) / 2;
  dst.y = screenRow * SQUARE_SIZE + (SQUARE_SIZE - surf->h) / 2;
  dst.w = surf->w;
  dst.h = surf->h;
  SDL_FreeSurface (surf);
  SDL_RenderCopy (renderer, tex, NULL, &dst);
  SDL_DestroyTexture (tex);
  return;
}

static void DrawBorderElement (int screenCol, int screenRow, char c)
{
  SDL_Rect square;

  /* Fill the *whole* square first -- not just a rectangle the size
     of this particular glyph, which is what this function used to
     do. Different characters in a proportional font are different
     widths and heights (a coordinate digit is not the same shape as
     a piece or a background-pattern glyph), so filling only that
     much of the square left everything outside it however it was
     before -- still whatever SDL_RenderClear() set it to, from the
     *previous* call in this same DrawBoard() pass, since nothing
     draws the squares themselves in one separate pass up front the
     way ../../demos/pgnviewer/pgnviewer.c does. */
  square.x = screenCol * SQUARE_SIZE;
  square.y = screenRow * SQUARE_SIZE;
  square.w = SQUARE_SIZE;
  square.h = SQUARE_SIZE;
  SDL_SetRenderDrawColor (renderer, LIGHT_SQUARE_R, LIGHT_SQUARE_G, LIGHT_SQUARE_B, 255);
  SDL_RenderFillRect (renderer, &square);

  DrawGlyph (screenCol, screenRow, c);

  return;
}

static void DrawPiece (int file, int rank, cpT cp)
{
  char text;
  SDL_Rect square;
  int screenRow = rank_8 - rank + 1;
  int screenCol = file + 1;
  int isLightSquare = (((file + rank) % 2) == 0);

  /* Every square, occupied or not, gets its own background pattern
     glyph first -- '+' for a light square, '*' for a dark one. This
     matters even for occupied squares: the piece glyphs below have
     a transparent surround (confirmed by rendering one in isolation
     against a checkered backdrop), not a background baked into
     them, so without drawing the real pattern underneath first, a
     piece would sit on whatever solid color was filled behind it
     instead of the correct light/dark pattern. */
  square.x = screenCol * SQUARE_SIZE;
  square.y = screenRow * SQUARE_SIZE;
  square.w = SQUARE_SIZE;
  square.h = SQUARE_SIZE;
  SDL_SetRenderDrawColor (renderer, LIGHT_SQUARE_R, LIGHT_SQUARE_G, LIGHT_SQUARE_B, 255);
  SDL_RenderFillRect (renderer, &square);          /* backdrop, in
                                                        case the
                                                        pattern glyph
                                                        itself isn't
                                                        100% opaque
                                                        right to its
                                                        own edges */
  DrawGlyph (screenCol, screenRow, isLightSquare ? '+' : '*');

  if (cp == cp_v0)
    return;                     /* nothing more to draw */

  /* The piece itself, composited on top of the pattern just drawn.
     Deliberately always the *lowercase* letter for whichever color
     this piece is, regardless of the square's own color -- this
     font's uppercase piece glyphs (originally meant, going by this
     project's own square-color convention, for pieces on a light
     square specifically) turn out to have leftover diagonal
     hatching baked directly into their own artwork, filling most of
     the glyph's bounding box -- confirmed by rendering every one of
     P/N/B/R/Q/K and M/O/V/T/W/L in isolation against a checkered
     backdrop, the same way the transparent lowercase glyphs were
     confirmed clean: every single uppercase piece letter in this
     font has it, not just some. That's a defect in the font itself
     (a leftover template mark from however its author originally
     drew each glyph, most likely -- see fonts/marroqui/'s own
     readme for the font's 1998 origin), not a mistake in how this
     program uses it, and the cleanest way to draw a genuinely
     correct board with it is simply never to ask it for an
     uppercase piece letter -- the lowercase ones, confirmed clean,
     plus the correct background pattern already drawn underneath,
     already gives the right final picture without it. */
  if (cv_c_cpv[cp] == c_w)
    switch (cv_p_cpv[cp])
      {
      case p_p: text = 'p'; break;
      case p_n: text = 'n'; break;
      case p_b: text = 'b'; break;
      case p_r: text = 'r'; break;
      case p_q: text = 'q'; break;
      case p_k: text = 'k'; break;
      default:
        printf ("[WARNING] Unexpected value: %d\n", cv_p_cpv[cp]);
        return;
      }
  else
    switch (cv_p_cpv[cp])
      {
      case p_p: text = 'o'; break;
      case p_n: text = 'm'; break;
      case p_b: text = 'v'; break;
      case p_r: text = 't'; break;
      case p_q: text = 'w'; break;
      case p_k: text = 'l'; break;
      default:
        printf ("[WARNING] Unexpected value: %d\n", cv_p_cpv[cp]);
        return;
      }

  DrawGlyph (screenCol, screenRow, text);

  return;
}

/*--> DrawBoard: redraw the current position (PgnSquareContents(),
      pgnlib.h) and a one-line status bar underneath it */
static void DrawBoard (PgnGameT *game)
{
  rankT rank;
  fileT file;
  sqT sq;
  SDL_Rect r;
  char status[512];
  SDL_Surface *surf;
  SDL_Texture *tex;
  SDL_Color white = { 255, 255, 255, 255 };
  SDL_SetRenderDrawColor (renderer, 0, 0, 0, 255);
  SDL_RenderClear (renderer);
  int i;
  
  /* Border */
  
  DrawBorderElement (0, 0, '1');
  DrawBorderElement (9, 0, '3');
  DrawBorderElement (0, 9, '7');
  DrawBorderElement (9, 9, '9');
  
  for (i = 0; i <= 7; i++)
  {
    DrawBorderElement (1 + i, 0, '2');
    DrawBorderElement (1 + i, 9, 200 + i);
    DrawBorderElement (9, 1 + i, '5');
    DrawBorderElement (0, 1 + i, 192 + i);
  }
  
  /* Pieces */
  
  for (rank = rank_8; rank >= rank_1; rank--)
    for (file = file_a; file <= file_h; file++)
    {
      sq = map_sq_rank_file (rank, file);
      DrawPiece (file, rank, PgnSquareContents (sq));
    };
  
  /* Status bar */
    
  snprintf (status, sizeof (status), "%s vs %s   (%s)   ply %d/%d", game->white, game->black, game->result, game->current_ply, game->move_count);
  
  surf = TTF_RenderText_Blended (font, status, white);
  
  if (surf != NULL)
  {
    tex = SDL_CreateTextureFromSurface (renderer, surf);
    r.x = 8;
    r.y = BOARD_PIXELS + (INFO_HEIGHT - surf->h) / 2;
    r.w = surf->w;
    r.h = surf->h;
    SDL_FreeSurface (surf);
    SDL_RenderCopy (renderer, tex, NULL, &r);
    SDL_DestroyTexture (tex);
  };
  
  SDL_RenderPresent (renderer);
  
  return;
}

static void SaveScreenshot (const char *path)
{
  void *pixels;
  int pitch;
  SDL_Surface *surf;

  pitch = WINDOW_W * 4;
  pixels = malloc ((size_t) pitch * WINDOW_H);
  if (pixels == NULL)
    {
      fprintf (stderr, "# screenshot: out of memory\n");
      return;
    };

  if (SDL_RenderReadPixels (renderer, NULL, SDL_PIXELFORMAT_ARGB8888,
                             pixels, pitch) != 0)
    {
      fprintf (stderr, "# screenshot: SDL_RenderReadPixels failed: %s\n",
               SDL_GetError ());
      free (pixels);
      return;
    };

  surf = SDL_CreateRGBSurfaceWithFormatFrom (pixels, WINDOW_W, WINDOW_H,
                                              32, pitch,
                                              SDL_PIXELFORMAT_ARGB8888);
  if (surf == NULL)
    {
      fprintf (stderr, "# screenshot: SDL_CreateRGBSurfaceWithFormatFrom "
               "failed: %s\n", SDL_GetError ());
      free (pixels);
      return;
    };

  if (SDL_SaveBMP (surf, path) != 0)
    fprintf (stderr, "# screenshot: SDL_SaveBMP failed: %s\n",
             SDL_GetError ());
  else
    fprintf (stderr, "# screenshot saved: %s\n", path);

  SDL_FreeSurface (surf);        /* just the SDL_Surface header --
                                     doesn't touch "pixels", which it
                                     never took ownership of */
  free (pixels);

  return;
}

static const char *WindowTitle (TTF_Font *font)
{
  static char title[128];
  snprintf (title, sizeof (title), "SAN Kit PGN Viewer (%s)", TTF_FontFaceFamilyName (font));
  return (title);
}

int main (int argc, char *argv[])
{
  PgnGameTextT *games;
  int ngames, current_game;
  PgnGameT game;
  SDL_Window *window;
  SDL_Event event;
  int running;
  
  if (argc != 2)
  {
    fprintf (stderr, "usage: %s file.pgn\n", argv[0]);
    return (1);
  };
  
  SANInit ();
  
  optnv[optn_nabd] = 1;
  
  optnv[optn_namd] = 1;
  
  ngames = PgnSplitFile (argv[1], &games);
  
  if (ngames <= 0)
  {
    fprintf (stderr, "%s: could not read any games from %s\n", argv[0], argv[1]);
    return (1);
  };
  
  current_game = 0;
  
  if (!PgnLoadGame (&games[current_game], &game))
  {
    fprintf (stderr, "%s: game 0 in %s did not parse\n", argv[0], argv[1]);
    return (1);
  };
  
  SDL_Init (SDL_INIT_VIDEO);
  
  TTF_Init ();
  
  {
    static const char *fontPaths[] =
    {
      "fonts/vollkorn-4-105/ttf/Vollkorn-Regular.ttf",
      NULL
    };
    int i;
    font = NULL;
    
    for (i = 0; (font == NULL) && (fontPaths[i] != NULL); i++)
      font = TTF_OpenFont (fontPaths[i], (int) (SQUARE_SIZE * 0.3));
      
    if (font == NULL)
    {
      fprintf (stderr, "%s: could not open font\n", argv[0]);
      return (1);
    };
    
    static const char *chessfontPaths[] =
    {
      //"fonts/adventur/avenfont.ttf", /* Chess Adventurer */
      //"fonts/alfonso/alfoches.ttf", /* Chess Alfonso-X */
      //"fonts/condal/condfont.ttf", /* Chess Condal */
      //"fonts/harlequi/harlfont.ttf", /* Chess Harlequin */
      //"fonts/kingdom/kingfont.ttf", /* Chess Kingdom */
      //"fonts/leipzig/leipfont.ttf", /* Chess Leipzig */
      //"fonts/line/linefont.ttf", /* Chess Line */
      //"fonts/lucena/lucefont.ttf", /* Chess Lucena */
      //"fonts/magnetic/magnfont.ttf", /* Chess Magnetic */
      //"fonts/mark/markfont.ttf", /* Chess Mark */
      "fonts/marroqui/marrfont.ttf", /* Chess Marroquin */
      //"fonts/medie/mvalfont.ttf", /* Chess Mediaeval */
      //"fonts/merid/merifont.ttf", /* Chess Merida */
      //"fonts/millennia/mildfont.ttf", /* Chess Millennia-D */
      //"fonts/millennia/millfont.ttf", /* Chess Millennia-L */
      //"fonts/motif/motifont.ttf", /* Chess Motif */
      NULL
    };
    
    chessfont = NULL;
    
    for (i = 0; (chessfont == NULL) && (chessfontPaths[i] != NULL); i++)
      chessfont = TTF_OpenFont (chessfontPaths[i], (int) (SQUARE_SIZE/* * 0.6*/));
      
    if (chessfont == NULL)
    {
      fprintf (stderr, "%s: could not open chess font\n", argv[0]);
      return (1);
    }
    else
    {
      printf("[INFO] Chess font family name: %s\n", TTF_FontFaceFamilyName(chessfont));
      printf("[INFO] Chess font style name: %s\n", TTF_FontFaceStyleName(chessfont));
    };
  };
  
  window = SDL_CreateWindow (
             WindowTitle (chessfont),
             SDL_WINDOWPOS_CENTERED,
             SDL_WINDOWPOS_CENTERED,
             WINDOW_W,
             WINDOW_H,
             SDL_WINDOW_SHOWN
           );
           
  renderer = SDL_CreateRenderer (window, -1, SDL_RENDERER_ACCELERATED);
  
  DrawBoard (&game);
  
  running = 1;
  
  while (running)
  {
    /* SDL_WaitEvent (rather than SDL_PollEvent in a busy loop) is
       the right choice here: this program only ever needs to
       redraw in response to a key press, never continuously */
    if (!SDL_WaitEvent (&event))
      continue;
      
    switch (event.type)
    {
    case SDL_QUIT:
      running = 0;
      break;
      
    case SDL_WINDOWEVENT:
      switch (event.window.event)
      {
      case SDL_WINDOWEVENT_EXPOSED:
      case SDL_WINDOWEVENT_SHOWN:
      case SDL_WINDOWEVENT_RESTORED:
        DrawBoard (&game);
        break;
        
      default:
        break;
      };
      
      break;
      
    case SDL_KEYDOWN:
      switch (event.key.keysym.sym)
      {
      case SDLK_RIGHT:
        PgnStepForward (&game);
        break;
        
      case SDLK_LEFT:
        PgnStepBackward (&game);
        break;
        
      case SDLK_HOME:
        PgnGotoPly (&game, 0);
        break;
        
      case SDLK_END:
        PgnGotoPly (&game, game.move_count);
        break;
        
      case SDLK_DOWN:
        if (current_game + 1 < ngames)
        {
          current_game++;
          PgnLoadGame (&games[current_game], &game);
        };
        
        break;
        
      case SDLK_UP:
        if (current_game > 0)
        {
          current_game--;
          PgnLoadGame (&games[current_game], &game);
        };
        
        break;
        
      case SDLK_c:
        {
          const char *fen = SANFENGen ();
          
          if (SDL_SetClipboardText (fen) == 0)
            fprintf (stderr, "# FEN copied to the clipboard: %s\n", fen);
          else
            fprintf (stderr, "# could not copy the FEN to the clipboard: %s\n", SDL_GetError ());
        };
        break;
      
      case SDLK_s:
        SaveScreenshot ("pgnviewer.bmp");
        break;
      
      case SDLK_ESCAPE:
      case SDLK_q:
        running = 0;
        break;
        
      default:
        break;
      };
      
      DrawBoard (&game);
      
      break;
      
    default:
      break;
    };
  };
  
  TTF_CloseFont (font);
  
  TTF_CloseFont (chessfont);
  
  TTF_Quit ();
  
  SDL_DestroyRenderer (renderer);
  
  SDL_DestroyWindow (window);
  
  SDL_Quit ();
  
  PgnFreeGameTexts (games, ngames);
  
  return (0);
}
