/* svg.c: Routines to capture ROM graphics statements to Scalable Vector
          Graphics files
   Copyright (c) 2014-2015 Stefano Bodrato
   Portions taken from svgwrite.c, (c) J.J. Green 2005

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

   Author contact information:

   E-mail: stefano@bodrato.it

*/

#include "config.h"

#ifdef HAVE_LIB_XML2
#include <errno.h>

#include <string.h>
#include <time.h>
#include <math.h>

#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>

#define ENCODING "utf-8"
#endif

#include "machine.h"
#include "memory_pages.h"
#include "svg.h"
#include "ui/ui.h"
#include "z80/z80.h"

#define SVG_NAME_LEN 128
#define BUFSZ 128

int svg_capture_active = 0;

#ifdef HAVE_LIB_XML2

static svg_capture_type svg_capture_mode = SVG_CAPTURE_LINES;

static char *svg_fname;
static char *svg_fnameroot;

static int svg_filecount;

static int svg_flag = 0;
static int svg_y_size = 176;

xmlBuffer* buffer;
xmlTextWriter* writer;


static const
libspectrum_byte palette[16][3] = {
                                    /*  R    G    B */
                                    {   0,   0,   0 },
                                    {   0,   0, 192 },
                                    { 192,   0,   0 },
                                    { 192,   0, 192 },
                                    {   0, 192,   0 },
                                    {   0, 192, 192 },
                                    { 200, 200,   0 },
                                    { 220, 220, 220 },
                                    {   0,   0,   0 },
                                    {   0,   0, 255 },
                                    { 255,   0,   0 },
                                    { 255,   0, 255 },
                                    {   0, 255,   0 },
                                    {   0, 255, 255 },
                                    { 255, 255,   0 },
                                    { 255, 255, 255 } };


/* handle error messages setting attributes */

static int
svg_attribute( const char *name, const char *value, const char *element)
{
  if( xmlTextWriterWriteAttribute( writer, BAD_CAST name, BAD_CAST value ) < 0 )
    return 1;

  return 0;
}

static const char*
timestring( void )
{
  time_t tm;
  char *tmstr;
  static char ts[25]; 

  time( &tm );
  tmstr = ctime( &tm );

  snprintf( ts, 25, "%.24s", tmstr );

  return ts;
}

static int
svg_write_metadata( void )
{
  if( xmlTextWriterStartElement( writer, BAD_CAST "metadata" ) < 0 )
    return 1;

  if( xmlTextWriterStartElement( writer, BAD_CAST "creator" ) < 0 )
    return 1;

  if( svg_attribute( "name", "fuse", "creator" ) != 0 )
    return 1;

  if( svg_attribute( "version", VERSION, "creator" ) != 0 )
    return 1;

  if( xmlTextWriterEndElement( writer ) < 0 )
    return 1;

  if( xmlTextWriterStartElement( writer, BAD_CAST "created" ) < 0 )
    return 1;

  if( svg_attribute( "date", timestring(), "created" ) != 0 )
    return 1;

  if( xmlTextWriterEndElement( writer ) < 0 )
    return 1;

  if( xmlTextWriterEndElement( writer ) < 0 )
    return 1;

  return 0;
}

static void
svg_openfile( void )
{
  int err;

  if( ( buffer = xmlBufferCreate() ) == NULL ) {
    ui_error( UI_ERROR_ERROR, "error creating the XML buffer" );
    return;
  }

  if( ( writer = xmlNewTextWriterMemory( buffer, 0 ) ) == NULL ) {
    ui_error( UI_ERROR_ERROR, "error allocating the XML writer" );
    return;
  }

  /*
  we free the writer at the start of each of
  these branches since this must be done before
  using the buffer
  */
    
  /* start xml */

  if( xmlTextWriterStartDocument( writer, NULL, ENCODING, NULL ) < 0 ) {
    ui_error( UI_ERROR_ERROR, "error on SVG document start" );
    return;
  }

  /* svg */

  if( xmlTextWriterStartElement( writer, BAD_CAST "svg" ) < 0 ) {
    ui_error( UI_ERROR_ERROR, "error on SVG open" );
    return;
  }

  err = 0;
  err += ( svg_attribute( "version", "1.1", "svg" ) != 0 );
  err += ( svg_attribute( "xmlns", "http://www.w3.org/2000/svg", "svg" ) != 0 );

  if( err ) {
    ui_error( UI_ERROR_ERROR, "error setting the SVG attributes" );
    return;
  }
}

static void
svg_rect( int xpos, int ypos, const char *xsize, const char *ysize,
          const char *opacity, const char *color )
{
  char path_element[ BUFSZ ];
  int err;

  if( xmlTextWriterStartElement( writer, BAD_CAST "rect" ) < 0 ) {
    ui_error( UI_ERROR_ERROR, "error creating a box in the SVG file" );
    return;
  }

  err = 0;
  snprintf( path_element, BUFSZ, "%d", xpos );
  err += ( svg_attribute( "x", path_element, "rect" ) != 0 );
  snprintf( path_element, BUFSZ, "%d", ypos );
  err += ( svg_attribute( "y", path_element, "rect" ) != 0 );
  err += ( svg_attribute( "width", xsize, "rect" ) != 0 );
  err += ( svg_attribute( "height", ysize, "rect" ) != 0 );
  err += ( svg_attribute( "opacity", opacity, "rect" ) != 0 );
  err += ( svg_attribute( "stroke", "none", "rect" ) != 0 );
  err += ( svg_attribute( "fill", color, "rect" ) != 0 );

  if( err ) {
    ui_error( UI_ERROR_ERROR, "error setting box coordinates in the SVG file" );
    return;
  }

  if( xmlTextWriterEndElement( writer ) < 0 ) {
    ui_error( UI_ERROR_ERROR,
              "error finishing a box creation in the SVG file" );
    return;
  }
}

static void
svg_openpath( void )
{
  int err;
  char svgcolor[ BUFSZ ];

  static int svg_paper;
  static int svg_ink;

  /* Background */
  svg_paper = ( ( readbyte_internal( 0x5c8d ) >> 3 ) & 15 ); /* ATTR_P */
  snprintf( svgcolor, BUFSZ, "rgb(%u,%u,%u)", palette[ svg_paper ][0],
                                              palette[ svg_paper ][1],
                                              palette[ svg_paper ][2] );

  svg_rect( 0, 0, "512", "368", "1", svgcolor );

  if( xmlTextWriterStartElement( writer, BAD_CAST "g" ) < 0 ) {
    ui_error( UI_ERROR_ERROR, "error opening the SVG element" );
    return;
  }

  svg_ink = ( readbyte_internal( 0x5c8d ) & 7 ) +
            ( ( readbyte_internal( 0x5c8d ) >> 3 ) & 8 );  /* ATTR_P */

  err = 0;
  snprintf( svgcolor, BUFSZ, "rgb(%u,%u,%u)", palette[ svg_ink ][0],
                                              palette[ svg_ink ][1],
                                              palette[ svg_ink ][2] );
  err += ( svg_attribute( "stroke", svgcolor, "g" ) != 0 );
  err += ( svg_attribute( "stroke-width", "1.7", "g" ) != 0 );

  if( err ) {
    ui_error( UI_ERROR_ERROR, "error setting the SVG element attributes" );
    return;
  }
}

static void
svg_closepath( void )
{
  if( xmlTextWriterEndElement( writer ) < 0 ) {
    ui_error( UI_ERROR_ERROR, "error on SVG element close" );
    return;
  }
}

static void
svg_closefile( void )
{
  FILE *fp;

  if( svg_write_metadata() != 0 ) {
    ui_error( UI_ERROR_ERROR, "error writing the SVG metadata" );
    return;
  }

  if( xmlTextWriterEndElement( writer ) < 0 ) {
    ui_error( UI_ERROR_ERROR, "error on SVG close" );
    return;
  }

  if( xmlTextWriterEndDocument( writer) < 0 ) {
    ui_error( UI_ERROR_ERROR, "error on SVG document end" );
    return;
  }

  xmlFreeTextWriter( writer );

  svg_fname = libspectrum_new( char, strlen( svg_fnameroot ) + BUFSIZ );
  snprintf( svg_fname, strlen( svg_fnameroot ) + BUFSIZ, "%s%d.svg", 
            svg_fnameroot, svg_filecount++ );

  if( ( fp = fopen( svg_fname, "w" ) ) != NULL ) {
    fprintf( fp, "%s", buffer->content );

    if( fclose( fp ) != 0 ) {
      ui_error( UI_ERROR_ERROR, "error closing SVG file '%s': %s", svg_fname,
                strerror( errno ) );
    }
  }

  libspectrum_free( svg_fname );
}



/* some init, open file (name)*/
void
svg_startcapture( const char *name, svg_capture_type mode )
{
  if( !svg_capture_active ) {

    svg_capture_mode = mode;

    if( name == NULL || *name == '\0' )
      name = "fuse";

    svg_fnameroot = libspectrum_new( char, strlen ( name ) );
    strcpy( svg_fnameroot, name );
    svg_filecount = 0;

    svg_capture_active = 1;
    svg_flag = 0;
    svg_y_size = 176;

    svg_openfile();
    svg_openpath();
  }
}

void
svg_stopcapture( void )
{
  if( svg_capture_active ) {
    svg_closepath();
    svg_closefile();

    libspectrum_free( svg_fnameroot );

    svg_capture_active = 0;
  }
}

static void
svg_capture_draw( void )
{
  char path_element[ BUFSZ ];
  char svgcolor[ BUFSZ ];
  int err;
  int dx,dy;
  int svg_ink;

  dx = z80.bc.b.l;
  dy = z80.bc.b.h;
  if( z80.de.b.l == 255 )
    dx = -dx;
  if( z80.de.b.h == 255 )
    dy = -dy;

  svg_ink = ( readbyte_internal( 0x5c8f ) & 7 ) +
            ( ( readbyte_internal( 0x5c8f ) >> 3 ) & 8 );  /* ATTR_T */
  snprintf( svgcolor, BUFSZ, "rgb(%u,%u,%u)", palette[ svg_ink ][0],
                                              palette[ svg_ink ][1],
                                              palette[ svg_ink ][2] );

  if( ( svg_capture_mode == SVG_CAPTURE_LINES ) &&
      ( ( z80.pc.w == 0x24ba ) || ( z80.pc.w == 0x2813 ) ) ) {
    /* DRAW */
    if( xmlTextWriterStartElement( writer, BAD_CAST "line" ) < 0 ) {
      ui_error( UI_ERROR_ERROR, "error creating the SVG line" );
      return;
    }
    err = 0;
    snprintf( path_element, BUFSZ, "%d", readbyte_internal( 0x5c7d ) * 2 );
    err += ( svg_attribute( "x1", path_element, "line" ) != 0 );
    snprintf( path_element, BUFSZ, "%d",
              ( svg_y_size - 1 - readbyte_internal( 0x5c7e ) ) * 2 );
    err += ( svg_attribute( "y1", path_element, "line" ) != 0 );

    snprintf( path_element, BUFSZ, "%d",
              ( readbyte_internal( 0x5c7d ) + dx ) * 2 );
    err += ( svg_attribute( "x2", path_element, "line" ) != 0 );
    snprintf( path_element, BUFSZ, "%d",
              ( ( svg_y_size - 1 - readbyte_internal( 0x5c7e ) - dy ) * 2 ) );
    err += ( svg_attribute( "y2", path_element, "line" ) != 0 );
    err += ( svg_attribute( "stroke", svgcolor, "line" ) != 0 );

  } else {
    /* PLOT */
    if( xmlTextWriterStartElement( writer, BAD_CAST "line" ) < 0 ) {
      ui_error( UI_ERROR_ERROR, "error creating the SVG DOT" );
      return;
    }
    err = 0;
    snprintf( path_element, BUFSZ, "%d", dx * 2 );
    err += ( svg_attribute( "x1", path_element, "line" ) != 0 );
    err += ( svg_attribute( "x2", path_element, "line" ) != 0 );
    snprintf( path_element, BUFSZ, "%d", ( ( svg_y_size - dy ) * 2 ) - 1 );
    err += ( svg_attribute( "y1", path_element, "line" ) != 0 );
    snprintf( path_element, BUFSZ, "%d", ( ( svg_y_size - dy ) * 2 ) - 2 );
    err += ( svg_attribute( "y2", path_element, "line" ) != 0 );
    err += ( svg_attribute( "stroke", svgcolor, "line" ) != 0 );
  }

  if( err ) {
    ui_error( UI_ERROR_ERROR, "error setting the SVG element coordinates" );
    return;
  }

  if( xmlTextWriterEndElement( writer ) < 0 ) {
    ui_error( UI_ERROR_ERROR,
              "error finishing the creation of the SVG element" );
    return;
  }

  svg_flag = 1;

  return;
}

static void
svg_byte( int xpos, int ypos, int udg_byte, char *color )
{
  int i;
  char path_element[ BUFSZ ];
  int err = 0;
  
  for( i = 7; i >= 0; i-- ) {
    if( udg_byte & 1 ) {
      if( xmlTextWriterStartElement( writer, BAD_CAST "circle" ) < 0 ) {
        ui_error( UI_ERROR_ERROR, "error creating an UDG dot in the SVG file" );
        return;
      }
      snprintf( path_element,BUFSZ, "%d", xpos + ( i * 2 ) );
      err += ( svg_attribute( "cx", path_element, "circle" ) != 0 );
      snprintf( path_element, BUFSZ, "%d", ypos + 1 );
      err += ( svg_attribute( "cy", path_element, "circle" ) != 0 );
      err += ( svg_attribute( "r", "0.17", "circle" ) != 0 );
      err += ( svg_attribute( "stroke", color, "circle" ) != 0 );
      err += ( svg_attribute( "fill", color, "circle" ) != 0 );

      if( err ) {
        ui_error( UI_ERROR_ERROR, "error setting the SVG UDG dot coordinates" );
        return;
      }

      if( xmlTextWriterEndElement( writer ) < 0 ) {
        ui_error( UI_ERROR_ERROR,
                  "error finishing the creation of a dot in the UDG element" );
        return;
      }
    }
    udg_byte /= 2;
  }
}

static void
svg_capture_char( void )
{
  char path_element[ BUFSZ ];
  char svgcolor_ink[ BUFSZ ];
  char svgcolor_paper[ BUFSZ ];
  int i, err;
  int svg_ink;
  int svg_paper;
  int x_pos, y_pos;
  int svg_char, udg_ptr;

  if( ( z80.de.b.l == 188 ) ||
      ( ( z80.de.b.l == 70 ) &&
        ( ( machine_current->machine == LIBSPECTRUM_MACHINE_TS2068 ) ||
          ( machine_current->machine == LIBSPECTRUM_MACHINE_TC2068 ) ) ) ) {
    x_pos = 33 - readbyte_internal( 0x5c88 );
    y_pos = ( svg_y_size / 8 ) + 3 - readbyte_internal( 0x5c89 );
    if( x_pos > 31 ) {
      x_pos = 0; y_pos++;
    }

    svg_ink = ( readbyte_internal( 0x5c8f ) & 7 ) +
              ( ( readbyte_internal( 0x5c8f ) >> 3 ) & 8 );  /* ATTR_T */
    svg_paper = ( ( readbyte_internal( 0x5c8f ) >> 3 ) & 15 ); /* ATTR_T */
    if( readbyte_internal( 0x5c91 ) & 4 ) {  /* INVERSE */
      svg_char = svg_paper;
      svg_paper = svg_ink;
      svg_ink = svg_char;
    }

    snprintf( svgcolor_paper, BUFSZ, "rgb(%u,%u,%u)", palette[ svg_paper ][0],
                                                      palette[ svg_paper ][1],
                                                      palette[ svg_paper ][2] );
    snprintf( svgcolor_ink, BUFSZ, "rgb(%u,%u,%u)", palette[ svg_ink ][0],
                                                    palette[ svg_ink ][1],
                                                    palette[ svg_ink ][2] );
    svg_char = z80.af.b.h;


    /* -- PAPER */
    if( svg_char < 165 )
      svg_rect( x_pos * 16, ( y_pos - 1 ) * 16, "16", "16", "0.4",
                svgcolor_paper );

    if( svg_char > 127 ) {

      if( svg_char < 144 ) {
        /* GRAPHICS BLOCKS */
        if( svg_char & 1 )
          svg_rect( 8 + x_pos * 16, ( y_pos - 1 ) * 16, "8", "8", "1",
                    svgcolor_ink );

        if( svg_char & 2 )
          svg_rect( x_pos * 16, ( y_pos - 1 ) * 16, "8", "8", "1",
                    svgcolor_ink );
        
        if( svg_char & 4 )
          svg_rect( 8 + x_pos * 16, 8 + ( y_pos - 1 ) * 16, "8", "8", "1",
                    svgcolor_ink );
        
        if( svg_char & 8 )
          svg_rect( x_pos * 16, 8 + ( y_pos - 1 ) * 16, "8", "8", "1",
                    svgcolor_ink );
      }
      else if( svg_char < 165 ) {
        /* 144-164 -> UDG "A"-"U" (pointed by $5C7B/$5C7C) */
        for( i = 0; i < 8; i++ ) {
          udg_ptr = ( readbyte_internal( 0x5c7c ) << 8 ) +
                      readbyte_internal( 0x5c7b );
          svg_byte( x_pos * 16, ( i * 2 ) + ( y_pos - 1 ) * 16,
                    readbyte_internal( udg_ptr + i + 8 * ( svg_char - 144 ) ),
                    svgcolor_ink );
        }
      }

    } else {


      /* -- INK and char */  

      if( xmlTextWriterStartElement( writer, BAD_CAST "text" ) < 0 ) {
        ui_error( UI_ERROR_ERROR, "error creating the SVG text element" );
        return;
      }

      err = 0;
      snprintf( path_element, BUFSZ, "%d", x_pos * 16 + 2 );
      err += ( svg_attribute( "x", path_element, "text" ) != 0 );
      snprintf( path_element, BUFSZ, "%d", y_pos * 16 - 3 );
      err += ( svg_attribute( "y", path_element, "text" ) != 0 );
      err += ( svg_attribute( "font-family", "monospace", "text" ) != 0 );
      err += ( svg_attribute( "stroke", svgcolor_ink, "text" ) != 0 );

      /* FLASH attribute */
      if( ( readbyte_internal( 0x5c8f ) & 128 ) != 0 ) {
        err += ( svg_attribute( "font-weight", "bold", "text" ) != 0 );
        err += ( svg_attribute( "stroke-width", "0.9", "text" ) != 0 );
        err += ( svg_attribute( "font-size", "21", "text" ) != 0 );
        err += ( svg_attribute( "fill", svgcolor_paper, "text" ) != 0 );
      } else {
        err += ( svg_attribute( "stroke-width", "0.7", "text" ) != 0 );
        err += ( svg_attribute( "font-size", "19", "text" ) != 0 );
        err += ( svg_attribute( "fill", svgcolor_ink, "text" ) != 0 );
      }

      if( err ) {
        ui_error( UI_ERROR_ERROR,
                  "error setting the SVG text element coordinates" );
        return;
      }

      if( svg_char == 127 ) /* (C) symbol */
          snprintf( path_element, BUFSZ, "%c%c", 0xc2, 0xa9 );
      else        /* Normal Character */
          snprintf( path_element, BUFSZ, "%c", svg_char );

      xmlTextWriterWriteString( writer, BAD_CAST path_element );

      if( xmlTextWriterEndElement( writer ) < 0 ) {
        ui_error( UI_ERROR_ERROR, "error closing the SVG text element" );
        return;
      }
    }

    svg_flag = 1;
  }

  return;
}

static void
svg_capture_cls( void )
{
  if( svg_flag ) {
    svg_closepath();
    svg_closefile();
    svg_openfile();
    svg_openpath();
  } else {
    /* This trick could load empty fake paths into the SVG file when CLS
       happens */
    svg_closepath();
    svg_openpath();
  }

  svg_flag = 0;
  svg_y_size = 176;

  return;
}

static void
svg_capture_scroll( void )
{
  char svgcolor[ BUFSZ ];
  static int svg_paper;

  /* Background */
  svg_paper = ( ( readbyte_internal( 0x5c8d ) >> 3 ) & 15 ); /* ATTR_P */
  snprintf( svgcolor, BUFSZ, "rgb(%u,%u,%u)", palette[ svg_paper ][0],
                                              palette[ svg_paper ][1], 
                                              palette[ svg_paper ][2] );

  svg_y_size += 8;
  svg_rect( 0, svg_y_size * 2, "512", "16", "1", svgcolor );
    return;
}

void
svg_capture( void )
{
  if( trap_check_rom( CHECK_48K_ROM ) ) {

    if( ( machine_current->machine == LIBSPECTRUM_MACHINE_TS2068 ) ||
        ( machine_current->machine == LIBSPECTRUM_MACHINE_TC2068 ) ) {
      if( ( svg_capture_mode == SVG_CAPTURE_DOTS ) && ( z80.pc.w == 0x263e ) )
        svg_capture_draw();

      if( ( svg_capture_mode == SVG_CAPTURE_LINES ) &&
          ( ( z80.pc.w == 0x2813 || z80.pc.w == 0x263e ) ) )
        svg_capture_draw();

      if( z80.pc.w == 0x63b )
        svg_capture_char();

      if( z80.pc.w == 0x08ea )
        svg_capture_cls();

      if( z80.pc.w == 0x0939 )
        svg_capture_scroll();
    } else {
        if( ( svg_capture_mode == SVG_CAPTURE_DOTS ) && ( z80.pc.w == 0x22e5 ) )
          svg_capture_draw();

        if( ( svg_capture_mode == SVG_CAPTURE_LINES ) &&
            ( ( z80.pc.w == 0x24ba || z80.pc.w == 0x22df ) ) )
          svg_capture_draw();

        if( z80.pc.w == 0x0b24 )
          svg_capture_char();

        if( z80.pc.w == 0x0daf )
          svg_capture_cls();

        if( z80.pc.w == 0x0e00 )
          svg_capture_scroll();
      }
    }
}

void
svg_capture_end( void )
{
  svg_stopcapture();
  return;
}

#else  /* !HAVE_LIB_XML2 */

void
svg_capture( void )
{
  return;
}

void
svg_capture_end( void )
{
  return;
}

#endif  /* HAVE_LIB_XML2 */
