/**
 * @file common_plotPS_functions.cpp
 * @brief Kicad: Common plot Postscript Routines
 */

#include <fctsys.h>
#include <trigo.h>
#include <wxstruct.h>
#include <base_struct.h>
#include <common.h>
#include <plot_common.h>
#include <macros.h>
#include <kicad_string.h>


/* Set the plot offset for the current plotting */
void PS_PLOTTER::set_viewport( wxPoint aOffset, double aScale, bool aMirror )
{
    wxASSERT( !output_file );
    plotMirror = aMirror;
    plot_offset  = aOffset;
    plot_scale   = aScale;
    device_scale = 1;               /* PS references in decimals */
    set_default_line_width( 100 );  /* default line width in 1/1000 inch */
}


/* Set the default line width (in 1/1000 inch) for the current plotting
 */
void PS_PLOTTER::set_default_line_width( int width )
{
    default_pen_width = width;   // line width in 1/1000 inch
    current_pen_width = -1;
}


/* Set the current line width (in 1/1000 inch) for the next plot
 */
void PS_PLOTTER::set_current_line_width( int width )
{
    wxASSERT( output_file );
    int pen_width;

    if( width >= 0 )
        pen_width = width;
    else
        pen_width = default_pen_width;

    if( pen_width != current_pen_width )
        fprintf( output_file, "%g setlinewidth\n",
                 user_to_device_size( pen_width ) );

    current_pen_width = pen_width;
}


/* Print the postscript set color command:
 * r g b setrgbcolor,
 * r, g, b  = color values (= 0 .. 1.0 )
 *
 * color = color index in ColorRefs[]
 */
void PS_PLOTTER::set_color( int color )
{
    wxASSERT( output_file );

    /* Return at invalid color index */
    if( color < 0 )
        return;

    if( color_mode )
    {
        if( negative_mode )
        {
            fprintf( output_file, "%.3g %.3g %.3g setrgbcolor\n",
                     (double) 1.0 - ColorRefs[color].m_Red / 255,
                     (double) 1.0 - ColorRefs[color].m_Green / 255,
                     (double) 1.0 - ColorRefs[color].m_Blue / 255 );
        }
        else
        {
            fprintf( output_file, "%.3g %.3g %.3g setrgbcolor\n",
                     (double) ColorRefs[color].m_Red / 255,
                     (double) ColorRefs[color].m_Green / 255,
                     (double) ColorRefs[color].m_Blue / 255 );
        }
    }
    else
    {
        /* B/W Mode - Use BLACK or WHITE for all items
         * note the 2 colors are used in B&W mode, mainly by Pcbnew to draw
         * holes in white on pads in black
         */
        int bwcolor = WHITE;
        if( color != WHITE )
            bwcolor = BLACK;
        if( negative_mode )
            fprintf( output_file, "%.3g %.3g %.3g setrgbcolor\n",
                     (double) 1.0 - ColorRefs[bwcolor].m_Red / 255,
                     (double) 1.0 - ColorRefs[bwcolor].m_Green / 255,
                     (double) 1.0 - ColorRefs[bwcolor].m_Blue / 255 );
        else
            fprintf( output_file, "%.3g %.3g %.3g setrgbcolor\n",
                     (double) ColorRefs[bwcolor].m_Red / 255,
                     (double) ColorRefs[bwcolor].m_Green / 255,
                     (double) ColorRefs[bwcolor].m_Blue / 255 );
    }
}


void PS_PLOTTER::set_dash( bool dashed )
{
    wxASSERT( output_file );
    if( dashed )
        fputs( "dashedline\n", stderr );
    else
        fputs( "solidline\n", stderr );
}


void PS_PLOTTER::rect( wxPoint p1, wxPoint p2, FILL_T fill, int width )
{
    user_to_device_coordinates( p1 );
    user_to_device_coordinates( p2 );

    set_current_line_width( width );
    fprintf( output_file, "%d %d %d %d rect%d\n", p1.x, p1.y,
             p2.x - p1.x, p2.y - p1.y, fill );
}


void PS_PLOTTER::circle( wxPoint pos, int diametre, FILL_T fill, int width )
{
    wxASSERT( output_file );
    user_to_device_coordinates( pos );
    double radius = user_to_device_size( diametre / 2.0 );

    if( radius < 1 )
        radius = 1;

    set_current_line_width( width );
    fprintf( output_file, "%d %d %g cir%d\n", pos.x, pos.y, radius, fill );
}


/* Plot an arc:
 * StAngle, EndAngle = start and end arc in 0.1 degree
 */
void PS_PLOTTER::arc( wxPoint centre, int StAngle, int EndAngle, int radius,
                      FILL_T fill, int width )
{
    wxASSERT( output_file );
    if( radius <= 0 )
        return;

    set_current_line_width( width );

    // Calculate start point.
    user_to_device_coordinates( centre );
    radius = wxRound( user_to_device_size( radius ) );
    if( plotMirror )
        fprintf( output_file, "%d %d %d %g %g arc%d\n", centre.x, centre.y,
                 radius, (double) -EndAngle / 10, (double) -StAngle / 10,
                 fill );
    else
        fprintf( output_file, "%d %d %d %g %g arc%d\n", centre.x, centre.y,
                 radius, (double) StAngle / 10, (double) EndAngle / 10,
                 fill );
}


/*
 * Function PlotPoly
 * Draw a polygon (filled or not) in POSTSCRIPT format
 * param aCornerList = corners list
 * param aFill :if true : filled polygon
 * param aWidth = line width
 */
void PS_PLOTTER::PlotPoly( std::vector< wxPoint >& aCornerList, FILL_T aFill, int aWidth )
{
    if( aCornerList.size() <= 1 )
        return;

    set_current_line_width( aWidth );

    wxPoint pos = aCornerList[0];
    user_to_device_coordinates( pos );
    fprintf( output_file, "newpath\n%d %d moveto\n", pos.x, pos.y );

    for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
    {
        pos = aCornerList[ii];
        user_to_device_coordinates( pos );
        fprintf( output_file, "%d %d lineto\n", pos.x, pos.y );
    }

    // Close path
    fprintf( output_file, "poly%d\n", aFill );
}

/*
 * Function PlotImage
 * Only some plotters can plot image bitmaps
 * for plotters that cannot plot a bitmap, a rectangle is plotted
 * param aImage = the bitmap
 * param aPos = position of the center of the bitmap
 * param aScaleFactor = the scale factor to apply to the bitmap size
 *                      (this is not the plot scale factor)
 */
void PS_PLOTTER::PlotImage( wxImage & aImage, wxPoint aPos, double aScaleFactor )
{
    wxSize pix_size;                // size of the bitmap in pixels
    pix_size.x = aImage.GetWidth();
    pix_size.y = aImage.GetHeight();
    wxSize drawsize;                // requested size of image
    drawsize.x = wxRound( aScaleFactor * pix_size.x );
    drawsize.y = wxRound( aScaleFactor * pix_size.y );

    // calculate the bottom left corner position of bitmap
    wxPoint start = aPos;
    start.x -= drawsize.x / 2;    // left
    start.y += drawsize.y / 2;    // bottom (Y axis reversed)

    // calculate the top right corner position of bitmap
    wxPoint end;
    end.x = start.x + drawsize.x;
    end.y = start.y - drawsize.y;

    fprintf( output_file, "/origstate save def\n" );
    fprintf( output_file, "/pix %d string def\n", pix_size.x );
    fprintf( output_file, "/greys %d string def\n", pix_size.x );

    // Locate lower-left corner of image
    user_to_device_coordinates( start );
    fprintf( output_file, "%d %d translate\n", start.x, start.y );
    // Map image size to device
    user_to_device_coordinates( end );
    fprintf( output_file, "%d %d scale\n",
            ABS(end.x - start.x), ABS(end.y - start.y));

    // Dimensions of source image (in pixels
    fprintf( output_file, "%d %d 8", pix_size.x, pix_size.y );
    //  Map unit square to source
    fprintf( output_file, " [%d 0 0 %d 0 %d]\n", pix_size.x, -pix_size.y , pix_size.y);
    // include image data in ps file
    fprintf( output_file, "{currentfile pix readhexstring pop}\n" );
    fprintf( output_file, "false 3 colorimage\n");
    // Single data source, 3 colors, Output RGB data (hexadecimal)
    int jj = 0;
    for( int yy = 0; yy < pix_size.y; yy ++ )
    {
        for( int xx = 0; xx < pix_size.x; xx++, jj++ )
        {
            if( jj >= 16 )
            {
                jj = 0;
                fprintf( output_file, "\n");
            }
            int red, green, blue;
            red = aImage.GetRed( xx, yy) & 0xFF;
            green = aImage.GetGreen( xx, yy) & 0xFF;
            blue = aImage.GetBlue( xx, yy) & 0xFF;
            fprintf( output_file, "%2.2X%2.2X%2.2X", red, green, blue);
        }
    }
    fprintf( output_file, "\n");
    fprintf( output_file, "origstate restore\n" );
}



/* Routine to draw to a new position
 */
void PS_PLOTTER::pen_to( wxPoint pos, char plume )
{
    wxASSERT( output_file );
    if( plume == 'Z' )
    {
        if( pen_state != 'Z' )
        {
            fputs( "stroke\n", output_file );
            pen_state     = 'Z';
            pen_lastpos.x = -1;
            pen_lastpos.y = -1;
        }
        return;
    }

    user_to_device_coordinates( pos );
    if( pen_state == 'Z' )
    {
        fputs( "newpath\n", output_file );
    }
    if( pen_state != plume || pos != pen_lastpos )
        fprintf( output_file,
                 "%d %d %sto\n",
                 pos.x,
                 pos.y,
                 ( plume=='D' ) ? "line" : "move" );
    pen_state   = plume;
    pen_lastpos = pos;
}


/* The code within this function (and the CloseFilePS function)
 * creates postscript files whose contents comply with Adobe's
 * Document Structuring Convention, as documented by assorted
 * details described within the following URLs:
 *
 * http://en.wikipedia.org/wiki/Document_Structuring_Conventions
 * http://partners.adobe.com/public/developer/en/ps/5001.DSC_Spec.pdf
 *
 *
 * BBox is the boundary box (position and size of the "client rectangle"
 * for drawings (page - margins) in mils (0.001 inch)
 */
bool PS_PLOTTER::start_plot( FILE* fout )
{
    wxASSERT( !output_file );
    wxString           msg;

    output_file = fout;
    static const char* PSMacro[] =
    {
        "/line {\n",
        "    newpath\n",
        "    moveto\n",
        "    lineto\n",
        "    stroke\n",
        "} bind def\n",
        "/cir0 { newpath 0 360 arc stroke } bind def\n",
        "/cir1 { newpath 0 360 arc gsave fill grestore stroke } bind def\n",
        "/cir2 { newpath 0 360 arc gsave fill grestore stroke } bind def\n",
        "/arc0 { newpath arc stroke } bind def\n",
        "/arc1 { newpath 4 index 4 index moveto arc closepath gsave fill ",
        "grestore stroke } bind def\n",
        "/arc2 { newpath 4 index 4 index moveto arc closepath gsave fill ",
        "grestore stroke } bind def\n",
        "/poly0 { stroke } bind def\n",
        "/poly1 { closepath gsave fill grestore stroke } bind def\n",
        "/poly2 { closepath gsave fill grestore stroke } bind def\n",
        "/rect0 { rectstroke } bind def\n",
        "/rect1 { rectfill } bind def\n",
        "/rect2 { rectfill } bind def\n",
        "/linemode0 { 0 setlinecap 0 setlinejoin 0 setlinewidth } bind def\n",
        "/linemode1 { 1 setlinecap 1 setlinejoin } bind def\n",
        "/dashedline { [50 50] 0 setdash } bind def\n",
        "/solidline { [] 0 setdash } bind def\n",
        "gsave\n",
        "0.0072 0.0072 scale\n",   // Configure postscript for decimals.
        "linemode1\n",
        NULL
    };

    const double       DECIMIL_TO_INCH = 0.0001;
    time_t             time1970 = time( NULL );

    fputs( "%!PS-Adobe-3.0\n", output_file );    // Print header

    fprintf( output_file, "%%%%Creator: %s\n", TO_UTF8( creator ) );

    // A "newline" character ("\n") is not included in the following string,
    // because it is provided by the ctime() function.
    fprintf( output_file, "%%%%CreationDate: %s", ctime( &time1970 ) );
    fprintf( output_file, "%%%%Title: %s\n", TO_UTF8( filename ) );
    fprintf( output_file, "%%%%Pages: 1\n" );
    fprintf( output_file, "%%%%PageOrder: Ascend\n" );

    // Print boundary box in 1/72 pixels per inch, box is in deci-mils
    const double CONV_SCALE = DECIMIL_TO_INCH * 72;

    // The coordinates of the lower left corner of the boundary
    // box need to be "rounded down", but the coordinates of its
    // upper right corner need to be "rounded up" instead.
    fprintf( output_file, "%%%%BoundingBox: 0 0 %d %d\n",
            (int) ceil( paper_size.y * CONV_SCALE ),
            (int) ceil( paper_size.x * CONV_SCALE ) );

    // Specify the size of the sheet and the name associated with that size.
    // (If the "User size" option has been selected for the sheet size,
    // identify the sheet size as "Custom" (rather than as "User"), but
    // otherwise use the name assigned by KiCad for each sheet size.)
    //
    // (The Document Structuring Convention also supports sheet weight,
    // sheet color, and sheet type properties being specified within a
    // %%DocumentMedia comment, but they are not being specified here;
    // a zero and two null strings are subsequently provided instead.)
    //
    // (NOTE: m_Size.y is *supposed* to be listed before m_Size.x;
    // the order in which they are specified is not wrong!)
    // Also note pageSize is given in mils, not in internal units and must be
    // converted to internal units.
    wxSize pageSize = pageInfo.GetSizeMils();

    if( pageInfo.IsCustom() )
        fprintf( output_file, "%%%%DocumentMedia: Custom %d %d 0 () ()\n",
                 wxRound( pageSize.y * 10 * CONV_SCALE ),
                 wxRound( pageSize.x * 10 * CONV_SCALE ) );

    else  // a standard paper size
        fprintf( output_file, "%%%%DocumentMedia: %s %d %d 0 () ()\n",
                 TO_UTF8( pageInfo.GetType() ),
                 wxRound( pageSize.y * 10 * CONV_SCALE ),
                 wxRound( pageSize.x * 10 * CONV_SCALE ) );

    fprintf( output_file, "%%%%Orientation: Landscape\n" );

    fprintf( output_file, "%%%%EndComments\n" );

    // Now specify various other details.

    // The following string has been specified here (rather than within
    // PSMacro[]) to highlight that it has been provided to ensure that the
    // contents of the postscript file comply with the details specified
    // within the Document Structuring Convention.
    fprintf( output_file, "%%%%Page: 1 1\n" );

    for( int ii = 0; PSMacro[ii] != NULL; ii++ )
    {
        fputs( PSMacro[ii], output_file );
    }

    // (If support for creating postscript files with a portrait orientation
    // is ever provided, determine whether it would be necessary to provide
    // an "else" command and then an appropriate "sprintf" command here.)
    fprintf( output_file, "%d 0 translate 90 rotate\n", paper_size.y );

    // Apply the scale adjustments
    if( plot_scale_adjX != 1.0 || plot_scale_adjY != 1.0 )
        fprintf( output_file, "%g %g scale\n",
                 plot_scale_adjX, plot_scale_adjY );

    // Set default line width ( g_Plot_DefaultPenWidth is in user units )
    fprintf( output_file, "%g setlinewidth\n",
             user_to_device_size( default_pen_width ) );

    return true;
}


bool PS_PLOTTER::end_plot()
{
    wxASSERT( output_file );
    fputs( "showpage\ngrestore\n%%EOF\n", output_file );
    fclose( output_file );
    output_file = NULL;

    return true;
}


/* Plot oval pad:
 * pos - Position of pad.
 * Dimensions dx, dy,
 * Orient Orient
 * The shape is drawn as a segment
 */
void PS_PLOTTER::flash_pad_oval( wxPoint pos, wxSize size, int orient,
                                 EDA_DRAW_MODE_T modetrace )
{
    wxASSERT( output_file );
    int x0, y0, x1, y1, delta;

    // The pad is reduced to an oval by dy > dx
    if( size.x > size.y )
    {
        EXCHG( size.x, size.y );
        orient += 900;
        if( orient >= 3600 )
            orient -= 3600;
    }

    delta = size.y - size.x;
    x0    = 0;
    y0    = -delta / 2;
    x1    = 0;
    y1    = delta / 2;
    RotatePoint( &x0, &y0, orient );
    RotatePoint( &x1, &y1, orient );

    if( modetrace == FILLED )
        thick_segment( wxPoint( pos.x + x0, pos.y + y0 ),
                       wxPoint( pos.x + x1, pos.y + y1 ), size.x, modetrace );
    else
        sketch_oval( pos, size, orient, -1 );
}


/* Plot round pad or via.
 */
void PS_PLOTTER::flash_pad_circle( wxPoint pos, int diametre,
                                   EDA_DRAW_MODE_T modetrace )
{
    int current_line_width;
    wxASSERT( output_file );

    set_current_line_width( -1 );
    current_line_width = get_current_line_width();
    if( current_line_width > diametre )
        current_line_width = diametre;

    if( modetrace == FILLED )
        circle( pos, diametre - current_pen_width, FILLED_SHAPE, current_line_width );
    else
        circle( pos, diametre - current_pen_width, NO_FILL, current_line_width );

    set_current_line_width( -1 );
}


/* Plot rectangular pad in any orientation.
 */
void PS_PLOTTER::flash_pad_rect( wxPoint pos, wxSize size,
                                 int orient, EDA_DRAW_MODE_T trace_mode )
{
    static std::vector< wxPoint > cornerList;
    cornerList.clear();

    set_current_line_width( -1 );
    int w = current_pen_width;
    size.x -= w;
    if( size.x < 1 )
        size.x = 1;
    size.y -= w;
    if( size.y < 1 )
        size.y = 1;

    int dx = size.x / 2;
    int dy = size.y / 2;

    wxPoint corner;
    corner.x = pos.x - dx;
    corner.y = pos.y + dy;
    cornerList.push_back( corner );
    corner.x = pos.x - dx;
    corner.y = pos.y - dy;
    cornerList.push_back( corner );
    corner.x = pos.x + dx;
    corner.y = pos.y - dy;
    cornerList.push_back( corner );
    corner.x = pos.x + dx;
    corner.y = pos.y + dy,
    cornerList.push_back( corner );

    for( unsigned ii = 0; ii < cornerList.size(); ii++ )
    {
        RotatePoint( &cornerList[ii], pos, orient );
    }

    cornerList.push_back( cornerList[0] );

    PlotPoly( cornerList, ( trace_mode == FILLED ) ? FILLED_SHAPE : NO_FILL );
}


/* Plot trapezoidal pad.
 * aPadPos is pad position, aCorners the corners position of the basic shape
 * Orientation aPadOrient in 0.1 degrees
 * Plot mode FILLED or SKETCH
 */
void PS_PLOTTER::flash_pad_trapez( wxPoint aPadPos, wxPoint aCorners[4],
                                   int aPadOrient, EDA_DRAW_MODE_T aTrace_Mode )
{
    static std::vector< wxPoint > cornerList;
    cornerList.clear();

    for( int ii = 0; ii < 4; ii++ )
        cornerList.push_back( aCorners[ii] );

    if( aTrace_Mode == FILLED )
    {
        set_current_line_width( 0 );
    }
    else
    {
        set_current_line_width( -1 );
        int w = current_pen_width;
        // offset polygon by w
        // coord[0] is assumed the lower left
        // coord[1] is assumed the upper left
        // coord[2] is assumed the upper right
        // coord[3] is assumed the lower right

        /* Trace the outline. */
        cornerList[0].x += w;
        cornerList[0].y -= w;
        cornerList[1].x += w;
        cornerList[1].y += w;
        cornerList[2].x -= w;
        cornerList[2].y += w;
        cornerList[3].x -= w;
        cornerList[3].y -= w;
    }

    for( int ii = 0; ii < 4; ii++ )
    {
        RotatePoint( &cornerList[ii], aPadOrient );
        cornerList[ii] += aPadPos;
    }

    cornerList.push_back( cornerList[0] );
    PlotPoly( cornerList, ( aTrace_Mode == FILLED ) ? FILLED_SHAPE : NO_FILL );
}