static int getFillId( FILL_T aFill )
{
    if( aFill == FILL_T::NO_FILL )
        return 0;

    if( aFill == FILL_T::FILLED_SHAPE )
        return 1;

    return 2;
}

/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 *
 */

/**
 * @file PS_plotter.cpp
 * @brief KiCad: specialized plotter for PS files format
 */

#include <convert_basic_shapes_to_polygon.h>
#include <macros.h>
#include <math/util.h>      // for KiROUND
#include <trigo.h>

#include <plotters/plotters_pslike.h>

/* Forward declaration of the font width metrics
   (yes extern! this is the way to forward declare variables */
extern const double hv_widths[256];
extern const double hvb_widths[256];
extern const double hvo_widths[256];
extern const double hvbo_widths[256];

const double PSLIKE_PLOTTER::postscriptTextAscent = 0.718;

// return a id used to select a ps macro (see StartPlot() ) from a FILL_TYPE
// fill mode, for arc, rect, circle and poly draw primitives

void PSLIKE_PLOTTER::SetColor( const COLOR4D& color )
{
    if( m_colorMode )
    {
        if( m_negativeMode )
            emitSetRGBColor( 1 - color.r, 1 - color.g, 1 - color.b );
        else
            emitSetRGBColor( color.r, color.g, color.b );
    }
    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
         */
        double k = 1; // White

        if( color != COLOR4D::WHITE )
            k = 0;

        if( m_negativeMode )
            emitSetRGBColor( 1 - k, 1 - k, 1 - k );
        else
            emitSetRGBColor( k, k, k );
    }
}

void PSLIKE_PLOTTER::FlashPadOval( const wxPoint& aPadPos, const wxSize& aSize,
                                   double aPadOrient, OUTLINE_MODE aTraceMode, void* aData )
{
    wxASSERT( m_outputFile );
    int x0, y0, x1, y1, delta;
    wxSize size( aSize );

    // The pad is reduced to an oval by dy > dx
    if( size.x > size.y )
    {
        std::swap( size.x, size.y );
        aPadOrient = AddAngles( aPadOrient, 900 );
    }

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

    if( aTraceMode == FILLED )
        ThickSegment( wxPoint( aPadPos.x + x0, aPadPos.y + y0 ),
                      wxPoint( aPadPos.x + x1, aPadPos.y + y1 ), size.x, aTraceMode, nullptr );
    else
        sketchOval( aPadPos, size, aPadOrient, -1 );
}

void PSLIKE_PLOTTER::FlashPadCircle( const wxPoint& aPadPos, int aDiameter,
                                     OUTLINE_MODE aTraceMode, void* aData )
{
    if( aTraceMode == FILLED )
    {
        Circle( aPadPos, aDiameter, FILL_T::FILLED_SHAPE, 0 );
    }
    else    // Plot a ring:
    {
        SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
        int linewidth = GetCurrentLineWidth();

        if( linewidth > aDiameter-2 )
            linewidth = aDiameter-2;

        Circle( aPadPos, aDiameter - linewidth, FILL_T::NO_FILL, linewidth );
    }

    SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
}

void PSLIKE_PLOTTER::FlashPadRect( const wxPoint& aPadPos, const wxSize& aSize,
                                   double aPadOrient, OUTLINE_MODE aTraceMode, void* aData )
{
    static std::vector< wxPoint > cornerList;
    wxSize size( aSize );
    cornerList.clear();

    if( aTraceMode == FILLED )
        SetCurrentLineWidth( 0 );
    else
        SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );

    size.x -= GetCurrentLineWidth();
    size.y -= GetCurrentLineWidth();

    if( size.x < 1 )
        size.x = 1;

    if( size.y < 1 )
        size.y = 1;

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

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

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

    cornerList.push_back( cornerList[0] );

    PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL,
              GetCurrentLineWidth() );
}

void PSLIKE_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize,
                                        int aCornerRadius, double aOrient,
                                        OUTLINE_MODE aTraceMode, void* aData )
{
    wxSize size( aSize );

    if( aTraceMode == FILLED )
    {
        SetCurrentLineWidth( 0 );
    }
    else
    {
        SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
        size.x -= GetCurrentLineWidth();
        size.y -= GetCurrentLineWidth();
        aCornerRadius -= GetCurrentLineWidth() / 2;
    }

    SHAPE_POLY_SET outline;
    TransformRoundChamferedRectToPolygon( outline, aPadPos, size, aOrient, aCornerRadius,
                                          0.0, 0, 0, GetPlotterArcHighDef(), ERROR_INSIDE );

    std::vector< wxPoint > cornerList;

    // TransformRoundRectToPolygon creates only one convex polygon
    SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
    cornerList.reserve( poly.PointCount() );

    for( int ii = 0; ii < poly.PointCount(); ++ii )
        cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );

    // Close polygon
    cornerList.push_back( cornerList[0] );

    PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL,
              GetCurrentLineWidth() );
}

void PSLIKE_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize,
                                     double aOrient, SHAPE_POLY_SET* aPolygons,
                                     OUTLINE_MODE aTraceMode, void* aData )
{
    wxSize size( aSize );

    if( aTraceMode == FILLED )
    {
        SetCurrentLineWidth( 0 );
    }
    else
    {
        SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
        size.x -= GetCurrentLineWidth();
        size.y -= GetCurrentLineWidth();
    }

    std::vector< wxPoint > cornerList;

    for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
    {
        SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
        cornerList.clear();

        for( int ii = 0; ii < poly.PointCount(); ++ii )
            cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );

        // Close polygon
        cornerList.push_back( cornerList[0] );

        PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL,
                  GetCurrentLineWidth() );
    }
}

void PSLIKE_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint *aCorners,
                                     double aPadOrient, OUTLINE_MODE aTraceMode, void* aData )
{
    static std::vector< wxPoint > cornerList;
    cornerList.clear();

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

    if( aTraceMode == FILLED )
    {
        SetCurrentLineWidth( 0 );
    }
    else
    {
        SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
        int w = GetCurrentLineWidth();

        // 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, ( aTraceMode == FILLED ) ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL,
              GetCurrentLineWidth() );
}

void PSLIKE_PLOTTER::FlashRegularPolygon( const wxPoint& aShapePos, int aRadius, int aCornerCount,
                                          double aOrient, OUTLINE_MODE aTraceMode, void* aData )
{
    // Do nothing
    wxASSERT( 0 );
}

std::string PSLIKE_PLOTTER::encodeStringForPlotter( const wxString& aUnicode )
{
    // Write on a std::string a string escaped for postscript/PDF
    std::string converted;

    converted += '(';

    for( unsigned i = 0; i < aUnicode.Len(); i++ )
    {
        // Laziness made me use stdio buffering yet another time...
        wchar_t ch = aUnicode[i];

        if( ch < 256 )
        {
            switch (ch)
            {
            // The ~ shouldn't reach the outside
            case '~':
                break;

            // These characters must be escaped
            case '(':
            case ')':
            case '\\':
                converted += '\\';
                KI_FALLTHROUGH;

            default:
                converted += ch;
                break;
            }
        }
    }

    converted += ')';

    return converted;
}

int PSLIKE_PLOTTER::returnPostscriptTextWidth( const wxString& aText, int aXSize,
                                               bool aItalic, bool aBold )
{
    const double *width_table = aBold ? ( aItalic ? hvbo_widths : hvb_widths )
                                      : ( aItalic ? hvo_widths : hv_widths );
    double tally = 0;

    for( unsigned i = 0; i < aText.length(); i++ )
    {
        wchar_t AsciiCode = aText[i];

        // Skip the negation marks and untabled points.
        if( AsciiCode != '~' && AsciiCode < 256 )
        {
            tally += width_table[AsciiCode];
        }
    }

    // Widths are proportional to height, but height is enlarged by a scaling factor.
    return KiROUND( aXSize * tally / postscriptTextAscent );
}

void PSLIKE_PLOTTER::postscriptOverlinePositions( const wxString& aText, int aXSize,
                                                  bool aItalic, bool aBold,
                                                  std::vector<int> *pos_pairs )
{
    /* XXX This function is *too* similar to returnPostscriptTextWidth.
       Consider merging them... */
    const double *width_table = aBold ? ( aItalic ? hvbo_widths : hvb_widths )
                                      : ( aItalic ? hvo_widths : hv_widths );
    double tally = 0;

    for( unsigned i = 0; i < aText.length(); i++ )
    {
        wchar_t AsciiCode = aText[i];

        // Skip the negation marks and untabled points
        if( AsciiCode != '~' && AsciiCode < 256 )
        {
            tally += width_table[AsciiCode];
        }
        else
        {
            if( AsciiCode == '~' )
                pos_pairs->push_back( KiROUND( aXSize * tally / postscriptTextAscent ) );
        }
    }

    // Special rule: we have to complete the last bar if the ~ aren't matched
    if( pos_pairs->size() % 2 == 1 )
        pos_pairs->push_back( KiROUND( aXSize * tally / postscriptTextAscent ) );
}

void PS_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
                              double aScale, bool aMirror )
{
    wxASSERT( !m_outputFile );
    m_plotMirror = aMirror;
    m_plotOffset = aOffset;
    m_plotScale = aScale;
    m_IUsPerDecimil = aIusPerDecimil;
    m_iuPerDeviceUnit = 1.0 / aIusPerDecimil;

    /* Compute the paper size in IUs */
    m_paperSize = m_pageInfo.GetSizeMils();
    m_paperSize.x *= 10.0 * aIusPerDecimil;
    m_paperSize.y *= 10.0 * aIusPerDecimil;
}

void PSLIKE_PLOTTER::computeTextParameters( const wxPoint&           aPos,
                                            const wxString&          aText,
                                            int                      aOrient,
                                            const wxSize&            aSize,
                                            bool                     aMirror,
                                            enum EDA_TEXT_HJUSTIFY_T aH_justify,
                                            enum EDA_TEXT_VJUSTIFY_T aV_justify,
                                            int                      aWidth,
                                            bool                     aItalic,
                                            bool                     aBold,
                                            double                   *wideningFactor,
                                            double                   *ctm_a,
                                            double                   *ctm_b,
                                            double                   *ctm_c,
                                            double                   *ctm_d,
                                            double                   *ctm_e,
                                            double                   *ctm_f,
                                            double                   *heightFactor )
{
    // Compute the starting position (compensated for alignment)
    wxPoint start_pos = aPos;

    // This is an approximation of the text bounds (in IUs)
    int tw = returnPostscriptTextWidth( aText, aSize.x, aItalic, aWidth );
    int th = aSize.y;
    int dx, dy;

    switch( aH_justify )
    {
    case GR_TEXT_HJUSTIFY_CENTER:
        dx = -tw / 2;
        break;

    case GR_TEXT_HJUSTIFY_RIGHT:
        dx = -tw;
        break;

    case GR_TEXT_HJUSTIFY_LEFT:
        dx = 0;
        break;
    }

    switch( aV_justify )
    {
    case GR_TEXT_VJUSTIFY_CENTER:
        dy = th / 2;
        break;

    case GR_TEXT_VJUSTIFY_TOP:
        dy = th;
        break;

    case GR_TEXT_VJUSTIFY_BOTTOM:
        dy = 0;
        break;
    }

    RotatePoint( &dx, &dy, aOrient );
    RotatePoint( &tw, &th, aOrient );
    start_pos.x += dx;
    start_pos.y += dy;
    DPOINT pos_dev = userToDeviceCoordinates( start_pos );
    DPOINT sz_dev = userToDeviceSize( aSize );

    // Now returns the final values... the widening factor
    *wideningFactor = sz_dev.x / sz_dev.y;

    // Mirrored texts must be plotted as mirrored!
    if( m_plotMirror )
    {
        *wideningFactor = -*wideningFactor;
        aOrient = -aOrient;
    }

    // The CTM transformation matrix
    double alpha = DECIDEG2RAD( aOrient );
    double sinalpha = sin( alpha );
    double cosalpha = cos( alpha );

    *ctm_a = cosalpha;
    *ctm_b = sinalpha;
    *ctm_c = -sinalpha;
    *ctm_d = cosalpha;
    *ctm_e = pos_dev.x;
    *ctm_f = pos_dev.y;

    // This is because the letters are less than 1 unit high
    *heightFactor = sz_dev.y / postscriptTextAscent;
}

void PS_PLOTTER::SetCurrentLineWidth( int aWidth, void* aData )
{
    wxASSERT( m_outputFile );

    if( aWidth == DO_NOT_SET_LINE_WIDTH )
        return;
    else if( aWidth == USE_DEFAULT_LINE_WIDTH )
        aWidth = m_renderSettings->GetDefaultPenWidth();
    else if( aWidth == 0 )
        aWidth = 1;

    wxASSERT_MSG( aWidth > 0, "Plotter called to set negative pen width" );

    if( aWidth != GetCurrentLineWidth() )
        fprintf( m_outputFile, "%g setlinewidth\n", userToDeviceSize( aWidth ) );

    m_currentPenWidth = aWidth;
}

void PS_PLOTTER::emitSetRGBColor( double r, double g, double b )
{
    wxASSERT( m_outputFile );

    fprintf( m_outputFile, "%.3g %.3g %.3g setrgbcolor\n", r, g, b );
}

void PS_PLOTTER::SetDash( PLOT_DASH_TYPE dashed )
{
    switch( dashed )
    {
    case PLOT_DASH_TYPE::DASH:
        fprintf( m_outputFile, "[%d %d] 0 setdash\n",
                 (int) GetDashMarkLenIU(), (int) GetDashGapLenIU() );
        break;
    case PLOT_DASH_TYPE::DOT:
        fprintf( m_outputFile, "[%d %d] 0 setdash\n",
                 (int) GetDotMarkLenIU(), (int) GetDashGapLenIU() );
        break;
    case PLOT_DASH_TYPE::DASHDOT:
        fprintf( m_outputFile, "[%d %d %d %d] 0 setdash\n",
                 (int) GetDashMarkLenIU(), (int) GetDashGapLenIU(),
                 (int) GetDotMarkLenIU(), (int) GetDashGapLenIU() );
        break;
    default:
        fputs( "solidline\n", m_outputFile );
    }
}

void PS_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width )
{
    DPOINT p1_dev = userToDeviceCoordinates( p1 );
    DPOINT p2_dev = userToDeviceCoordinates( p2 );

    SetCurrentLineWidth( width );
    fprintf( m_outputFile, "%g %g %g %g rect%d\n", p1_dev.x, p1_dev.y,
             p2_dev.x - p1_dev.x, p2_dev.y - p1_dev.y, getFillId( fill ) );
}

void PS_PLOTTER::Circle( const wxPoint& pos, int diametre, FILL_T fill, int width )
{
    wxASSERT( m_outputFile );
    DPOINT pos_dev = userToDeviceCoordinates( pos );
    double radius = userToDeviceSize( diametre / 2.0 );

    SetCurrentLineWidth( width );
    fprintf( m_outputFile, "%g %g %g cir%d\n", pos_dev.x, pos_dev.y, radius, getFillId( fill ) );
}

void PS_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
                      FILL_T fill, int width )
{
    wxASSERT( m_outputFile );

    if( radius <= 0 )
        return;

    if( StAngle > EndAngle )
        std::swap( StAngle, EndAngle );

    SetCurrentLineWidth( width );

    // Calculate start point.
    DPOINT centre_dev = userToDeviceCoordinates( centre );
    double radius_dev = userToDeviceSize( radius );

    if( m_plotMirror )
    {
        if( m_mirrorIsHorizontal )
        {
            StAngle = 1800.0 -StAngle;
            EndAngle = 1800.0 -EndAngle;
            std::swap( StAngle, EndAngle );
        }
        else
        {
            StAngle = -StAngle;
            EndAngle = -EndAngle;
        }
    }

    fprintf( m_outputFile, "%g %g %g %g %g arc%d\n", centre_dev.x, centre_dev.y,
             radius_dev, StAngle / 10.0, EndAngle / 10.0, getFillId( fill ) );
}

void PS_PLOTTER::PlotPoly( const std::vector< wxPoint >& aCornerList, FILL_T aFill,
                           int aWidth, void * aData )
{
    if( aCornerList.size() <= 1 )
        return;

    SetCurrentLineWidth( aWidth );

    DPOINT pos = userToDeviceCoordinates( aCornerList[0] );
    fprintf( m_outputFile, "newpath\n%g %g moveto\n", pos.x, pos.y );

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

    // Close/(fill) the path
    fprintf( m_outputFile, "poly%d\n", getFillId( aFill ) );
}

void PS_PLOTTER::PlotImage( const wxImage& aImage, const wxPoint& aPos, double aScaleFactor )
{
    wxSize pix_size;                // size of the bitmap in pixels
    pix_size.x = aImage.GetWidth();
    pix_size.y = aImage.GetHeight();
    DPOINT drawsize( aScaleFactor * pix_size.x,
                     aScaleFactor * pix_size.y ); // requested size of image

    // 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( m_outputFile, "/origstate save def\n" );
    fprintf( m_outputFile, "/pix %d string def\n", pix_size.x );

    // Locate lower-left corner of image
    DPOINT start_dev = userToDeviceCoordinates( start );
    fprintf( m_outputFile, "%g %g translate\n", start_dev.x, start_dev.y );

    // Map image size to device
    DPOINT end_dev = userToDeviceCoordinates( end );
    fprintf( m_outputFile, "%g %g scale\n",
             std::abs(end_dev.x - start_dev.x), std::abs(end_dev.y - start_dev.y));

    // Dimensions of source image (in pixels
    fprintf( m_outputFile, "%d %d 8", pix_size.x, pix_size.y );

    //  Map unit square to source
    fprintf( m_outputFile, " [%d 0 0 %d 0 %d]\n", pix_size.x, -pix_size.y , pix_size.y);

    // include image data in ps file
    fprintf( m_outputFile, "{currentfile pix readhexstring pop}\n" );

    if( m_colorMode )
        fputs( "false 3 colorimage\n", m_outputFile );
    else
        fputs( "image\n", m_outputFile );

    // Single data source, 3 colors, Output RGB data (hexadecimal)
    // (or the same downscaled to gray)
    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( m_outputFile, "\n");
            }

            int red, green, blue;
            red = aImage.GetRed( xx, yy) & 0xFF;
            green = aImage.GetGreen( xx, yy) & 0xFF;
            blue = aImage.GetBlue( xx, yy) & 0xFF;

            // PS doesn't support alpha, so premultiply against white background
            if( aImage.HasAlpha() )
            {
                unsigned char alpha = aImage.GetAlpha( xx, yy ) & 0xFF;

                if( alpha < 0xFF )
                {
                    float a = 1.0 - ( (float) alpha / 255.0 );
                    red =   ( int )( red   + ( a * 0xFF ) ) & 0xFF;
                    green = ( int )( green + ( a * 0xFF ) ) & 0xFF;
                    blue =  ( int )( blue  + ( a * 0xFF ) ) & 0xFF;
                }
            }

            if( aImage.HasMask() )
            {
                if( red == aImage.GetMaskRed() && green == aImage.GetMaskGreen()
                        && blue == aImage.GetMaskBlue() )
                {
                    red = 0xFF;
                    green = 0xFF;
                    blue = 0xFF;
                }
            }

            if( m_colorMode )
            {
                fprintf( m_outputFile, "%2.2X%2.2X%2.2X", red, green, blue );
            }
            else
            {
                unsigned char grey = KiROUND( red * 0.2126 + green * 0.7152 + blue * 0.0722 );

                fprintf( m_outputFile, "%2.2X", grey );
            }
        }
    }

    fprintf( m_outputFile, "\n");
    fprintf( m_outputFile, "origstate restore\n" );
}

void PS_PLOTTER::PenTo( const wxPoint& pos, char plume )
{
    wxASSERT( m_outputFile );

    if( plume == 'Z' )
    {
        if( m_penState != 'Z' )
        {
            fputs( "stroke\n", m_outputFile );
            m_penState     = 'Z';
            m_penLastpos.x = -1;
            m_penLastpos.y = -1;
        }

        return;
    }

    if( m_penState == 'Z' )
    {
        fputs( "newpath\n", m_outputFile );
    }

    if( m_penState != plume || pos != m_penLastpos )
    {
        DPOINT pos_dev = userToDeviceCoordinates( pos );
        fprintf( m_outputFile, "%g %g %sto\n",
                 pos_dev.x, pos_dev.y,
                 ( plume=='D' ) ? "line" : "move" );
    }

    m_penState   = plume;
    m_penLastpos = pos;
}

bool PS_PLOTTER::StartPlot()
{
    wxASSERT( m_outputFile );
    wxString           msg;

    static const char* PSMacro[] =
    {
    "%%BeginProlog\n",
    "/line { newpath moveto lineto stroke } 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\n",
    "    grestore stroke } bind def\n",
    "/arc2 { newpath 4 index 4 index moveto arc closepath gsave fill\n",
    "    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 { [200] 100 setdash } bind def\n",
    "/solidline { [] 0 setdash } bind def\n",

    // This is for 'hidden' text (search anchors for PDF)
    "/phantomshow { moveto\n",
    "    /KicadFont findfont 0.000001 scalefont setfont\n",
    "    show } bind def\n",

    // This is for regular postscript text
    "/textshow { gsave\n",
    "    findfont exch scalefont setfont concat 1 scale 0 0 moveto show\n",
    "    } bind def\n",

    // Utility for getting Latin1 encoded fonts
    "/reencodefont {\n",
    "  findfont dup length dict begin\n",
    "  { 1 index /FID ne\n",
    "    { def }\n",
    "    { pop pop } ifelse\n",
    "  } forall\n",
    "  /Encoding ISOLatin1Encoding def\n",
    "  currentdict\n",
    "  end } bind def\n"

    // Remap AdobeStandard fonts to Latin1
    "/KicadFont /Helvetica reencodefont definefont pop\n",
    "/KicadFont-Bold /Helvetica-Bold reencodefont definefont pop\n",
    "/KicadFont-Oblique /Helvetica-Oblique reencodefont definefont pop\n",
    "/KicadFont-BoldOblique /Helvetica-BoldOblique reencodefont definefont pop\n",
    "%%EndProlog\n",
    nullptr
    };

    time_t time1970 = time( nullptr );

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

    fprintf( m_outputFile, "%%%%Creator: %s\n", TO_UTF8( m_creator ) );

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

    // Print boundary box in 1/72 pixels per inch, box is in mils
    const double BIGPTsPERMIL = 0.072;

    /* 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. */
    wxSize psPaperSize = m_pageInfo.GetSizeMils();

    if( !m_pageInfo.IsPortrait() )
        psPaperSize.Set( m_pageInfo.GetHeightMils(), m_pageInfo.GetWidthMils() );

    fprintf( m_outputFile, "%%%%BoundingBox: 0 0 %d %d\n",
             (int) ceil( psPaperSize.x * BIGPTsPERMIL ),
             (int) ceil( psPaperSize.y * BIGPTsPERMIL ) );

    // 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.

    if( m_pageInfo.IsCustom() )
    {
        fprintf( m_outputFile, "%%%%DocumentMedia: Custom %d %d 0 () ()\n",
                 KiROUND( psPaperSize.x * BIGPTsPERMIL ),
                 KiROUND( psPaperSize.y * BIGPTsPERMIL ) );
    }
    else  // a standard paper size
    {
        fprintf( m_outputFile, "%%%%DocumentMedia: %s %d %d 0 () ()\n",
                 TO_UTF8( m_pageInfo.GetType() ),
                 KiROUND( psPaperSize.x * BIGPTsPERMIL ),
                 KiROUND( psPaperSize.y * BIGPTsPERMIL ) );
    }

    if( m_pageInfo.IsPortrait() )
        fprintf( m_outputFile, "%%%%Orientation: Portrait\n" );
    else
        fprintf( m_outputFile, "%%%%Orientation: Landscape\n" );

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

    // Now specify various other details.

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

    // 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.
    fputs( "%%Page: 1 1\n"
           "%%BeginPageSetup\n"
           "gsave\n"
           "0.0072 0.0072 scale\n"    // Configure postscript for decimils coordinates
           "linemode1\n", m_outputFile );

    // Rototranslate the coordinate to achieve the landscape layout
    if( !m_pageInfo.IsPortrait() )
        fprintf( m_outputFile, "%d 0 translate 90 rotate\n", 10 * psPaperSize.x );

    // Apply the user fine scale adjustments
    if( plotScaleAdjX != 1.0 || plotScaleAdjY != 1.0 )
        fprintf( m_outputFile, "%g %g scale\n", plotScaleAdjX, plotScaleAdjY );

    // Set default line width
    fprintf( m_outputFile, "%g setlinewidth\n",
             userToDeviceSize( m_renderSettings->GetDefaultPenWidth() ) );
    fputs( "%%EndPageSetup\n", m_outputFile );

    return true;
}

bool PS_PLOTTER::EndPlot()
{
    wxASSERT( m_outputFile );
    fputs( "showpage\n"
           "grestore\n"
           "%%EOF\n", m_outputFile );
    fclose( m_outputFile );
    m_outputFile = nullptr;

    return true;
}

void PS_PLOTTER::Text( const wxPoint&       aPos,
                const COLOR4D&              aColor,
                const wxString&             aText,
                double                      aOrient,
                const wxSize&               aSize,
                enum EDA_TEXT_HJUSTIFY_T    aH_justify,
                enum EDA_TEXT_VJUSTIFY_T    aV_justify,
                int                         aWidth,
                bool                        aItalic,
                bool                        aBold,
                bool                        aMultilineAllowed,
                void*                       aData )
{
    SetCurrentLineWidth( aWidth );
    SetColor( aColor );

    // Draw the hidden postscript text (if requested)
    if( m_textMode == PLOT_TEXT_MODE::PHANTOM )
    {
        std::string ps_test = encodeStringForPlotter( aText );
        DPOINT pos_dev = userToDeviceCoordinates( aPos );
        fprintf( m_outputFile, "%s %g %g phantomshow\n", ps_test.c_str(), pos_dev.x, pos_dev.y );
    }

    PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, aWidth,
                   aItalic, aBold, aMultilineAllowed );
}

/**
 * Character widths for Helvetica
 */
const double hv_widths[256] = {
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.355, 0.556, 0.556, 0.889, 0.667, 0.191,
    0.333, 0.333, 0.389, 0.584, 0.278, 0.333, 0.278, 0.278,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556,
    0.556, 0.556, 0.278, 0.278, 0.584, 0.584, 0.584, 0.556,
    1.015, 0.667, 0.667, 0.722, 0.722, 0.667, 0.611, 0.778,
    0.722, 0.278, 0.500, 0.667, 0.556, 0.833, 0.722, 0.778,
    0.667, 0.778, 0.722, 0.667, 0.611, 0.722, 0.667, 0.944,
    0.667, 0.667, 0.611, 0.278, 0.278, 0.278, 0.469, 0.556,
    0.333, 0.556, 0.556, 0.500, 0.556, 0.556, 0.278, 0.556,
    0.556, 0.222, 0.222, 0.500, 0.222, 0.833, 0.556, 0.556,
    0.556, 0.556, 0.333, 0.500, 0.278, 0.556, 0.500, 0.722,
    0.500, 0.500, 0.500, 0.334, 0.260, 0.334, 0.584, 0.278,
    0.278, 0.278, 0.222, 0.556, 0.333, 1.000, 0.556, 0.556,
    0.333, 1.000, 0.667, 0.333, 1.000, 0.278, 0.278, 0.278,
    0.278, 0.222, 0.222, 0.333, 0.333, 0.350, 0.556, 1.000,
    0.333, 1.000, 0.500, 0.333, 0.944, 0.278, 0.278, 0.667,
    0.278, 0.333, 0.556, 0.556, 0.556, 0.556, 0.260, 0.556,
    0.333, 0.737, 0.370, 0.556, 0.584, 0.333, 0.737, 0.333,
    0.400, 0.584, 0.333, 0.333, 0.333, 0.556, 0.537, 0.278,
    0.333, 0.333, 0.365, 0.556, 0.834, 0.834, 0.834, 0.611,
    0.667, 0.667, 0.667, 0.667, 0.667, 0.667, 1.000, 0.722,
    0.667, 0.667, 0.667, 0.667, 0.278, 0.278, 0.278, 0.278,
    0.722, 0.722, 0.778, 0.778, 0.778, 0.778, 0.778, 0.584,
    0.778, 0.722, 0.722, 0.722, 0.722, 0.667, 0.667, 0.611,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.889, 0.500,
    0.556, 0.556, 0.556, 0.556, 0.278, 0.278, 0.278, 0.278,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.584,
    0.611, 0.556, 0.556, 0.556, 0.556, 0.500, 0.556, 0.500
};

/**
 * Character widths for Helvetica-Bold
 */
const double hvb_widths[256] = {
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.333, 0.474, 0.556, 0.556, 0.889, 0.722, 0.238,
    0.333, 0.333, 0.389, 0.584, 0.278, 0.333, 0.278, 0.278,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556,
    0.556, 0.556, 0.333, 0.333, 0.584, 0.584, 0.584, 0.611,
    0.975, 0.722, 0.722, 0.722, 0.722, 0.667, 0.611, 0.778,
    0.722, 0.278, 0.556, 0.722, 0.611, 0.833, 0.722, 0.778,
    0.667, 0.778, 0.722, 0.667, 0.611, 0.722, 0.667, 0.944,
    0.667, 0.667, 0.611, 0.333, 0.278, 0.333, 0.584, 0.556,
    0.333, 0.556, 0.611, 0.556, 0.611, 0.556, 0.333, 0.611,
    0.611, 0.278, 0.278, 0.556, 0.278, 0.889, 0.611, 0.611,
    0.611, 0.611, 0.389, 0.556, 0.333, 0.611, 0.556, 0.778,
    0.556, 0.556, 0.500, 0.389, 0.280, 0.389, 0.584, 0.278,
    0.278, 0.278, 0.278, 0.556, 0.500, 1.000, 0.556, 0.556,
    0.333, 1.000, 0.667, 0.333, 1.000, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.500, 0.500, 0.350, 0.556, 1.000,
    0.333, 1.000, 0.556, 0.333, 0.944, 0.278, 0.278, 0.667,
    0.278, 0.333, 0.556, 0.556, 0.556, 0.556, 0.280, 0.556,
    0.333, 0.737, 0.370, 0.556, 0.584, 0.333, 0.737, 0.333,
    0.400, 0.584, 0.333, 0.333, 0.333, 0.611, 0.556, 0.278,
    0.333, 0.333, 0.365, 0.556, 0.834, 0.834, 0.834, 0.611,
    0.722, 0.722, 0.722, 0.722, 0.722, 0.722, 1.000, 0.722,
    0.667, 0.667, 0.667, 0.667, 0.278, 0.278, 0.278, 0.278,
    0.722, 0.722, 0.778, 0.778, 0.778, 0.778, 0.778, 0.584,
    0.778, 0.722, 0.722, 0.722, 0.722, 0.667, 0.667, 0.611,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.889, 0.556,
    0.556, 0.556, 0.556, 0.556, 0.278, 0.278, 0.278, 0.278,
    0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.584,
    0.611, 0.611, 0.611, 0.611, 0.611, 0.556, 0.611, 0.556
};

/**
 * Character widths for Helvetica-Oblique
 */
const double hvo_widths[256] = {
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.355, 0.556, 0.556, 0.889, 0.667, 0.191,
    0.333, 0.333, 0.389, 0.584, 0.278, 0.333, 0.278, 0.278,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556,
    0.556, 0.556, 0.278, 0.278, 0.584, 0.584, 0.584, 0.556,
    1.015, 0.667, 0.667, 0.722, 0.722, 0.667, 0.611, 0.778,
    0.722, 0.278, 0.500, 0.667, 0.556, 0.833, 0.722, 0.778,
    0.667, 0.778, 0.722, 0.667, 0.611, 0.722, 0.667, 0.944,
    0.667, 0.667, 0.611, 0.278, 0.278, 0.278, 0.469, 0.556,
    0.333, 0.556, 0.556, 0.500, 0.556, 0.556, 0.278, 0.556,
    0.556, 0.222, 0.222, 0.500, 0.222, 0.833, 0.556, 0.556,
    0.556, 0.556, 0.333, 0.500, 0.278, 0.556, 0.500, 0.722,
    0.500, 0.500, 0.500, 0.334, 0.260, 0.334, 0.584, 0.278,
    0.278, 0.278, 0.222, 0.556, 0.333, 1.000, 0.556, 0.556,
    0.333, 1.000, 0.667, 0.333, 1.000, 0.278, 0.278, 0.278,
    0.278, 0.222, 0.222, 0.333, 0.333, 0.350, 0.556, 1.000,
    0.333, 1.000, 0.500, 0.333, 0.944, 0.278, 0.278, 0.667,
    0.278, 0.333, 0.556, 0.556, 0.556, 0.556, 0.260, 0.556,
    0.333, 0.737, 0.370, 0.556, 0.584, 0.333, 0.737, 0.333,
    0.400, 0.584, 0.333, 0.333, 0.333, 0.556, 0.537, 0.278,
    0.333, 0.333, 0.365, 0.556, 0.834, 0.834, 0.834, 0.611,
    0.667, 0.667, 0.667, 0.667, 0.667, 0.667, 1.000, 0.722,
    0.667, 0.667, 0.667, 0.667, 0.278, 0.278, 0.278, 0.278,
    0.722, 0.722, 0.778, 0.778, 0.778, 0.778, 0.778, 0.584,
    0.778, 0.722, 0.722, 0.722, 0.722, 0.667, 0.667, 0.611,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.889, 0.500,
    0.556, 0.556, 0.556, 0.556, 0.278, 0.278, 0.278, 0.278,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.584,
    0.611, 0.556, 0.556, 0.556, 0.556, 0.500, 0.556, 0.500
};

/**
 * Character widths for Helvetica-BoldOblique
 */
const double hvbo_widths[256] = {
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278,
    0.278, 0.333, 0.474, 0.556, 0.556, 0.889, 0.722, 0.238,
    0.333, 0.333, 0.389, 0.584, 0.278, 0.333, 0.278, 0.278,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556,
    0.556, 0.556, 0.333, 0.333, 0.584, 0.584, 0.584, 0.611,
    0.975, 0.722, 0.722, 0.722, 0.722, 0.667, 0.611, 0.778,
    0.722, 0.278, 0.556, 0.722, 0.611, 0.833, 0.722, 0.778,
    0.667, 0.778, 0.722, 0.667, 0.611, 0.722, 0.667, 0.944,
    0.667, 0.667, 0.611, 0.333, 0.278, 0.333, 0.584, 0.556,
    0.333, 0.556, 0.611, 0.556, 0.611, 0.556, 0.333, 0.611,
    0.611, 0.278, 0.278, 0.556, 0.278, 0.889, 0.611, 0.611,
    0.611, 0.611, 0.389, 0.556, 0.333, 0.611, 0.556, 0.778,
    0.556, 0.556, 0.500, 0.389, 0.280, 0.389, 0.584, 0.278,
    0.278, 0.278, 0.278, 0.556, 0.500, 1.000, 0.556, 0.556,
    0.333, 1.000, 0.667, 0.333, 1.000, 0.278, 0.278, 0.278,
    0.278, 0.278, 0.278, 0.500, 0.500, 0.350, 0.556, 1.000,
    0.333, 1.000, 0.556, 0.333, 0.944, 0.278, 0.278, 0.667,
    0.278, 0.333, 0.556, 0.556, 0.556, 0.556, 0.280, 0.556,
    0.333, 0.737, 0.370, 0.556, 0.584, 0.333, 0.737, 0.333,
    0.400, 0.584, 0.333, 0.333, 0.333, 0.611, 0.556, 0.278,
    0.333, 0.333, 0.365, 0.556, 0.834, 0.834, 0.834, 0.611,
    0.722, 0.722, 0.722, 0.722, 0.722, 0.722, 1.000, 0.722,
    0.667, 0.667, 0.667, 0.667, 0.278, 0.278, 0.278, 0.278,
    0.722, 0.722, 0.778, 0.778, 0.778, 0.778, 0.778, 0.584,
    0.778, 0.722, 0.722, 0.722, 0.722, 0.667, 0.667, 0.611,
    0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.889, 0.556,
    0.556, 0.556, 0.556, 0.556, 0.278, 0.278, 0.278, 0.278,
    0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.584,
    0.611, 0.611, 0.611, 0.611, 0.611, 0.556, 0.611, 0.556
};


try
            {
                pckCert = std::make_shared<dcap::parser::x509::PckCertificate>(dcap::parser::x509::PckCertificate(*cert));
            }
            catch (const dcap::parser::FormatException&)
            {
                return STATUS_UNSUPPORTED_CERT_FORMAT;
            }
            catch (const dcap::parser::InvalidExtensionException&)
            {
                return STATUS_SGX_PCK_INVALID_EXTENSIONS;
            }

/*
 *
 *
 */

#include "CertificateChain.h"

#include "X509Constants.h"

#include <algorithm>

namespace intel { namespace sgx { namespace dcap {

Status CertificateChain::parse(const std::string& pemCertChain)
{
    const auto certStrs = splitChain(pemCertChain);

    certs.reserve(certStrs.size());
    for(const auto& certPem : certStrs)
    {
        try {
            auto cert = std::make_shared<dcap::parser::x509::Certificate>(dcap::parser::x509::Certificate::parse(certPem));

            if (cert->getSubject() == cert->getIssuer())
            {
                rootCert = cert;
            }

            certs.emplace_back(cert);
        }
        // any cert in chain has wrong format
        // then whole chain should be considered invalid
        catch (const dcap::parser::FormatException&)
        {
            return STATUS_UNSUPPORTED_CERT_FORMAT;
        }
        catch (const dcap::parser::InvalidExtensionException&)
        {
            // Since cert order in the chain is not deterministic
            // we do not know which cert we failed to parse (subject is also an extension)
            // Is that RootCA? IntermediateCA? PCKCert? TCBSigningCert?
            // We will do some guessing... I mean some heuristics
            if (certs.empty()) // we failed parsing first cert, in most cases it will be a root CA
            {
                return STATUS_SGX_ROOT_CA_INVALID_EXTENSIONS;
            }
            if (certs.size() == 1) // second cert wll be probably an intermediate CA
            {
                if (certStrs.size() == 2)
                {
                    return STATUS_SGX_TCB_SIGNING_CERT_INVALID_EXTENSIONS;
                }
                return STATUS_SGX_INTERMEDIATE_CA_INVALID_EXTENSIONS;
            }
            else // third cert wll be probably PCK CA
            {
                return STATUS_SGX_PCK_INVALID_EXTENSIONS;
            }
        }
    }

    for(auto const &cert: certs)
    {
        auto signedCertIter = std::find_if(certs.cbegin(), certs.cend(), [cert](const std::shared_ptr<const dcap::parser::x509::Certificate> &found)
        {
            return found->getSubject() != cert->getSubject()
                   && found->getIssuer() == cert->getSubject();
        });
        if(signedCertIter == certs.cend())
        {
            topmostCert = cert;
        }
        if (cert->getSubject().getCommonName().find(constants::SGX_PCK_CN_PHRASE) != std::string::npos)
        {


int main(int argc, char *argv[]) {
    ros::init(argc, argv, "sliding_window_object_detector_trainer_node");
    ROS_INFO("RUNNING NODELET %s", "sliding_window_object_detector_trainer");
    jsk_perception::SlidingWindowObjectDetectorTrainer run_trainer;
    ros::spin();
    return 0;
}

#include <jsk_topic_tools/log_utils.h>
#include <jsk_perception/sliding_window_object_detector_trainer.h>

#include <iostream>
namespace jsk_perception
{
   SlidingWindowObjectDetectorTrainer::SlidingWindowObjectDetectorTrainer()
#if CV_MAJOR_VERSION < 3
     : supportVectorMachine_(new cv::SVM)
#endif
   {
#if CV_MAJOR_VERSION >= 3
      this->supportVectorMachine_ = cv::ml::SVM::create();
#endif
      nh_.getParam("dataset_path", this->dataset_path_);
      nh_.getParam("object_dataset_filename", this->object_dataset_filename_);
      nh_.param<std::string>("object_dataset_topic", this->object_dataset_topic_, "/dataset/roi");
      nh_.getParam("nonobject_dataset_filename", this->nonobject_dataset_filename_);
      nh_.param<std::string>("nonobject_dataset_topic", this->nonobject_dataset_topic_, "/dataset/background/roi");
      nh_.getParam("classifier_name", this->trained_classifier_name_);
      nh_.getParam("swindow_x", this->swindow_x_);
      nh_.getParam("swindow_y", this->swindow_y_);
      nh_.param<std::string>("manifest_filename", this->manifest_filename_, "sliding_window_trainer_manifest.xml");

      ROS_INFO("--Training Classifier");
      std::string pfilename = dataset_path_ + this->object_dataset_filename_;
      std::string nfilename = dataset_path_ + this->nonobject_dataset_filename_;
      trainObjectClassifier(pfilename, nfilename);
      ROS_INFO("--Trained Successfully..");

      /*write the training manifest*/
      std::string manifest_filename = this->manifest_filename_;
      cv::FileStorage fs(manifest_filename, cv::FileStorage::WRITE);
      this->writeTrainingManifestToDirectory(fs);
      fs.release();

      cv::destroyAllWindows();
      nh_.shutdown();
   }

   void SlidingWindowObjectDetectorTrainer::trainObjectClassifier(
      std::string pfilename, std::string nfilename)
   {
      cv::Mat featureMD;
      cv::Mat labelMD;
      std::string topic_name = this->object_dataset_topic_;
      this->readDataset(pfilename, topic_name, featureMD, labelMD, true, 1);
      ROS_INFO("Info: Total Object Sample: %d", featureMD.rows);

      topic_name = this->nonobject_dataset_topic_;
      this->readDataset(nfilename, topic_name, featureMD, labelMD, true, -1);
      ROS_INFO("Info: Total Training Features: %d", featureMD.rows);

      try {
         this->trainBinaryClassSVM(featureMD, labelMD);
         this->supportVectorMachine_->save(
            this->trained_classifier_name_.c_str());
      } catch(std::exception &e) {
         ROS_ERROR("--ERROR: PLEASE CHECK YOUR DATA \n%s", e.what());
         std::_Exit(EXIT_FAILURE);
      }
   }

   void SlidingWindowObjectDetectorTrainer::readDataset(
      std::string filename, std::string topic_name, cv::Mat &featureMD,
      cv::Mat &labelMD, bool is_usr_label, const int usr_label) {
      ROS_INFO("--READING DATASET IMAGE");
      try {
         rosbag_ = boost::shared_ptr<rosbag::Bag>(new rosbag::Bag);
         this->rosbag_->open(filename, rosbag::bagmode::Read);
         ROS_INFO("Bag Found and Opened Successfully...");
         std::vector<std::string> topics;
         topics.push_back(std::string(topic_name));
         rosbag::View view(*rosbag_, rosbag::TopicQuery(topics));
         BOOST_FOREACH(rosbag::MessageInstance const m, view) {
            sensor_msgs::Image::ConstPtr img_msg = m.instantiate<
               sensor_msgs::Image>();
            cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(
               img_msg, sensor_msgs::image_encodings::BGR8);
            if (cv_ptr->image.data) {
               cv::Mat image = cv_ptr->image.clone();
               float label = static_cast<float>(usr_label);
               labelMD.push_back(label);
               this->extractFeatures(image, featureMD);
               cv::imshow("image", image);
               cv::waitKey(3);
            } else {
               ROS_WARN("-> NO IMAGE");
            }
         }
         this->rosbag_->close();
      } catch (ros::Exception &e) {
         ROS_ERROR("ERROR: Bag File:%s not found..\n%s",
                   filename.c_str(), e.what());
         std::_Exit(EXIT_FAILURE);
      }
   }

/**
 * currently programmed using fixed sized image
 */
   void SlidingWindowObjectDetectorTrainer::extractFeatures(
      cv::Mat &img, cv::Mat &featureMD) {
      ROS_INFO("--EXTRACTING IMAGE FEATURES.");
      if (img.data) {
         cv::resize(img, img, cv::Size(this->swindow_x_, this->swindow_y_));
         cv::Mat hog_feature = this->computeHOG(img);
         cv::Mat hsv_feature;
         this->computeHSHistogram(img, hsv_feature, 16, 16, true);
         hsv_feature = hsv_feature.reshape(1, 1);
         cv::Mat _feature;
         this->concatenateCVMat(hog_feature, hsv_feature, _feature, true);
         featureMD.push_back(_feature);
      }
      cv::imshow("image", img);
      cv::waitKey(3);
   }

   void SlidingWindowObjectDetectorTrainer::trainBinaryClassSVM(
      const cv::Mat &featureMD, const cv::Mat &labelMD)
   {
      ROS_INFO("--TRAINING CLASSIFIER");
#if CV_MAJOR_VERSION >= 3
      this->supportVectorMachine_->setType(cv::ml::SVM::NU_SVC);
      this->supportVectorMachine_->setDegree(0.0);
      this->supportVectorMachine_->setGamma(0.90);
      this->supportVectorMachine_->setCoef0(0.50);
      this->supportVectorMachine_->setC(1);
      this->supportVectorMachine_->setNu(0.70);
      this->supportVectorMachine_->setP(1.0);
      cv::TermCriteria term_crit;
      term_crit.type = CV_TERMCRIT_ITER | CV_TERMCRIT_EPS;
      term_crit.maxCount = 1e6;
      term_crit.epsilon = 1e-6;
      this->supportVectorMachine_->setTermCriteria(term_crit);
      cv::ml::ParamGrid paramGrid = cv::ml::ParamGrid();
      paramGrid.minVal = 0;
      paramGrid.maxVal = 0;
      paramGrid.logStep = 1;

      cv::Ptr<cv::ml::TrainData> train = cv::ml::TrainData::create(featureMD, cv::ml::ROW_SAMPLE, labelMD, cv::Mat(), cv::Mat()); // ROW_SAMPLE ? COL_SAMPLE ?
      this->supportVectorMachine_->trainAuto
        (train, 10,
         paramGrid, cv::ml::SVM::getDefaultGrid(cv::ml::SVM::GAMMA),
         cv::ml::SVM::getDefaultGrid(cv::ml::SVM::P),
         cv::ml::SVM::getDefaultGrid(cv::ml::SVM::NU),
         cv::ml::SVM::getDefaultGrid(cv::ml::SVM::COEF),
         cv::ml::SVM::getDefaultGrid(cv::ml::SVM::DEGREE),
          true);
#else
      cv::SVMParams svm_param = cv::SVMParams();
      svm_param.svm_type = cv::SVM::NU_SVC;
      svm_param.kernel_type = cv::SVM::RBF;
      svm_param.degree = 0.0;
      svm_param.gamma = 0.90;
      svm_param.coef0 = 0.50;
      svm_param.C = 1;
      svm_param.nu = 0.70;
      svm_param.p = 1.0;
      svm_param.class_weights = NULL;
      svm_param.term_crit.type = CV_TERMCRIT_ITER | CV_TERMCRIT_EPS;
      svm_param.term_crit.max_iter = 1e6;
      svm_param.term_crit.epsilon = 1e-6;
      cv::ParamGrid paramGrid = cv::ParamGrid();
      paramGrid.min_val = 0;
      paramGrid.max_val = 0;
      paramGrid.step = 1;

      /*this->supportVectorMachine_->train(
        featureMD, labelMD, cv::Mat(), cv::Mat(), svm_param);*/
      this->supportVectorMachine_->train_auto
         (featureMD, labelMD, cv::Mat(), cv::Mat(), svm_param, 10,
          paramGrid, cv::SVM::get_default_grid(cv::SVM::GAMMA),
          cv::SVM::get_default_grid(cv::SVM::P),
          cv::SVM::get_default_grid(cv::SVM::NU),
          cv::SVM::get_default_grid(cv::SVM::COEF),
          cv::SVM::get_default_grid(cv::SVM::DEGREE),
          true);
#endif
   }

   void SlidingWindowObjectDetectorTrainer::concatenateCVMat(
      const cv::Mat &mat_1, const cv::Mat &mat_2,
      cv::Mat &featureMD, bool iscolwise)
   {
      if (iscolwise) {
         featureMD = cv::Mat(mat_1.rows, (mat_1.cols + mat_2.cols), CV_32F);
         for (int i = 0; i < featureMD.rows; i++) {
            for (int j = 0; j < mat_1.cols; j++) {
               featureMD.at<float>(i, j) = mat_1.at<float>(i, j);
            }
            for (int j = mat_1.cols; j < featureMD.cols; j++) {
               featureMD.at<float>(i, j) = mat_2.at<float>(i, j - mat_1.cols);
            }
         }
      } else {
         featureMD = cv::Mat((mat_1.rows + mat_2.rows), mat_1.cols, CV_32F);
         for (int i = 0; i < featureMD.cols; i++) {
            for (int j = 0; j < mat_1.rows; j++) {
               featureMD.at<float>(j, i) = mat_1.at<float>(j, i);
            }
            for (int j = mat_1.rows; j < featureMD.rows; j++) {
               featureMD.at<float>(j, i) = mat_2.at<float>(j - mat_1.rows, i);
            }
         }
      }
   }

   void SlidingWindowObjectDetectorTrainer::writeTrainingManifestToDirectory(
      cv::FileStorage &fs)
   {
      fs <<  "TrainerInfo" << "{";
      fs << "trainer_type" << "cv::SVM";
      fs << "trainer_path" << this->trained_classifier_name_;
      fs << "}";

      fs <<  "FeatureInfo" << "{";
      fs << "HOG" << 1;
      fs << "COLOR_HISTOGRAM" << 1;
      fs << "}";

      fs <<  "SlidingWindowInfo" << "{";
      fs << "swindow_x" << this->swindow_x_;
      fs << "swindow_y" << this->swindow_y_;
      fs << "}";

      fs << "TrainingDatasetDirectoryInfo" << "{";
      fs << "object_dataset_filename" << this->object_dataset_filename_;
      fs << "nonobject_dataset_filename" << this->nonobject_dataset_filename_;
      fs << "dataset_path" << this->dataset_path_;  // only path to neg
      fs << "}";
   }

   void SlidingWindowObjectDetectorTrainer::computeHSHistogram(
      cv::Mat &src, cv::Mat &hist, const int hBin, const int sBin, bool is_norm)
   {
      if (src.empty()) {
         return;
      }
      cv::Mat hsv;
      cv::cvtColor(src, hsv, CV_BGR2HSV);
      int histSize[] = {hBin, sBin};
      float h_ranges[] = {0, 180};
      float s_ranges[] = {0, 256};
      const float* ranges[] = {h_ranges, s_ranges};
      int channels[] = {0, 1};
      cv::calcHist(
         &hsv, 1, channels, cv::Mat(), hist, 2, histSize, ranges, true, false);
      if (is_norm) {
         cv::normalize(hist, hist, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());
      }
   }
}  // namespace jsk_perception


try {
         this->trainBinaryClassSVM(featureMD, labelMD);
         this->supportVectorMachine_->save(
            this->trained_classifier_name_.c_str());
      } catch(std::exception &e) {
         ROS_ERROR("--ERROR: PLEASE CHECK YOUR DATA \n%s", e.what());
         std::_Exit(EXIT_FAILURE);
      }

#include <jsk_topic_tools/log_utils.h>
#include <jsk_perception/sliding_window_object_detector_trainer.h>

#include <iostream>
namespace jsk_perception
{
   SlidingWindowObjectDetectorTrainer::SlidingWindowObjectDetectorTrainer()
#if CV_MAJOR_VERSION < 3
     : supportVectorMachine_(new cv::SVM)
#endif
   {
#if CV_MAJOR_VERSION >= 3
      this->supportVectorMachine_ = cv::ml::SVM::create();
#endif
      nh_.getParam("dataset_path", this->dataset_path_);
      nh_.getParam("object_dataset_filename", this->object_dataset_filename_);
      nh_.param<std::string>("object_dataset_topic", this->object_dataset_topic_, "/dataset/roi");
      nh_.getParam("nonobject_dataset_filename", this->nonobject_dataset_filename_);
      nh_.param<std::string>("nonobject_dataset_topic", this->nonobject_dataset_topic_, "/dataset/background/roi");
      nh_.getParam("classifier_name", this->trained_classifier_name_);
      nh_.getParam("swindow_x", this->swindow_x_);
      nh_.getParam("swindow_y", this->swindow_y_);
      nh_.param<std::string>("manifest_filename", this->manifest_filename_, "sliding_window_trainer_manifest.xml");

      ROS_INFO("--Training Classifier");
      std::string pfilename = dataset_path_ + this->object_dataset_filename_;
      std::string nfilename = dataset_path_ + this->nonobject_dataset_filename_;
      trainObjectClassifier(pfilename, nfilename);
      ROS_INFO("--Trained Successfully..");

      /*write the training manifest*/
      std::string manifest_filename = this->manifest_filename_;
      cv::FileStorage fs(manifest_filename, cv::FileStorage::WRITE);
      this->writeTrainingManifestToDirectory(fs);
      fs.release();

      cv::destroyAllWindows();
      nh_.shutdown();
   }

   void SlidingWindowObjectDetectorTrainer::trainObjectClassifier(
      std::string pfilename, std::string nfilename)
   {
      cv::Mat featureMD;
      cv::Mat labelMD;
      std::string topic_name = this->object_dataset_topic_;
      this->readDataset(pfilename, topic_name, featureMD, labelMD, true, 1);
      ROS_INFO("Info: Total Object Sample: %d", featureMD.rows);

      topic_name = this->nonobject_dataset_topic_;
      this->readDataset(nfilename, topic_name, featureMD, labelMD, true, -1);
      ROS_INFO("Info: Total Training Features: %d", featureMD.rows);

   }

   void SlidingWindowObjectDetectorTrainer::readDataset(
      std::string filename, std::string topic_name, cv::Mat &featureMD,
      cv::Mat &labelMD, bool is_usr_label, const int usr_label) {
      ROS_INFO("--READING DATASET IMAGE");
      try {
         rosbag_ = boost::shared_ptr<rosbag::Bag>(new rosbag::Bag);
         this->rosbag_->open(filename, rosbag::bagmode::Read);
         ROS_INFO("Bag Found and Opened Successfully...");
         std::vector<std::string> topics;
         topics.push_back(std::string(topic_name));
         rosbag::View view(*rosbag_, rosbag::TopicQuery(topics));
         BOOST_FOREACH(rosbag::MessageInstance const m, view) {
            sensor_msgs::Image::ConstPtr img_msg = m.instantiate<
               sensor_msgs::Image>();
            cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(
               img_msg, sensor_msgs::image_encodings::BGR8);
            if (cv_ptr->image.data) {
               cv::Mat image = cv_ptr->image.clone();
               float label = static_cast<float>(usr_label);
               labelMD.push_back(label);
               this->extractFeatures(image, featureMD);
               cv::imshow("image", image);
               cv::waitKey(3);
            } else {
               ROS_WARN("-> NO IMAGE");
            }
         }
         this->rosbag_->close();
      } catch (ros::Exception &e) {
         ROS_ERROR("ERROR: Bag File:%s not found..\n%s",
                   filename.c_str(), e.what());
         std::_Exit(EXIT_FAILURE);
      }
   }

/**
 * currently programmed using fixed sized image
 */
   void SlidingWindowObjectDetectorTrainer::extractFeatures(
      cv::Mat &img, cv::Mat &featureMD) {
      ROS_INFO("--EXTRACTING IMAGE FEATURES.");
      if (img.data) {
         cv::resize(img, img, cv::Size(this->swindow_x_, this->swindow_y_));
         cv::Mat hog_feature = this->computeHOG(img);
         cv::Mat hsv_feature;
         this->computeHSHistogram(img, hsv_feature, 16, 16, true);
         hsv_feature = hsv_feature.reshape(1, 1);
         cv::Mat _feature;
         this->concatenateCVMat(hog_feature, hsv_feature, _feature, true);
         featureMD.push_back(_feature);
      }
      cv::imshow("image", img);
      cv::waitKey(3);
   }

   void SlidingWindowObjectDetectorTrainer::trainBinaryClassSVM(
      const cv::Mat &featureMD, const cv::Mat &labelMD)
   {
      ROS_INFO("--TRAINING CLASSIFIER");
#if CV_MAJOR_VERSION >= 3
      this->supportVectorMachine_->setType(cv::ml::SVM::NU_SVC);
      this->supportVectorMachine_->setDegree(0.0);
      this->supportVectorMachine_->setGamma(0.90);
      this->supportVectorMachine_->setCoef0(0.50);
      this->supportVectorMachine_->setC(1);
      this->supportVectorMachine_->setNu(0.70);
      this->supportVectorMachine_->setP(1.0);
      cv::TermCriteria term_crit;
      term_crit.type = CV_TERMCRIT_ITER | CV_TERMCRIT_EPS;
      term_crit.maxCount = 1e6;
      term_crit.epsilon = 1e-6;
      this->supportVectorMachine_->setTermCriteria(term_crit);
      cv::ml::ParamGrid paramGrid = cv::ml::ParamGrid();
      paramGrid.minVal = 0;
      paramGrid.maxVal = 0;
      paramGrid.logStep = 1;

      cv::Ptr<cv::ml::TrainData> train = cv::ml::TrainData::create(featureMD, cv::ml::ROW_SAMPLE, labelMD, cv::Mat(), cv::Mat()); // ROW_SAMPLE ? COL_SAMPLE ?
      this->supportVectorMachine_->trainAuto
        (train, 10,
         paramGrid, cv::ml::SVM::getDefaultGrid(cv::ml::SVM::GAMMA),
         cv::ml::SVM::getDefaultGrid(cv::ml::SVM::P),
         cv::ml::SVM::getDefaultGrid(cv::ml::SVM::NU),
         cv::ml::SVM::getDefaultGrid(cv::ml::SVM::COEF),
         cv::ml::SVM::getDefaultGrid(cv::ml::SVM::DEGREE),
          true);
#else
      cv::SVMParams svm_param = cv::SVMParams();
      svm_param.svm_type = cv::SVM::NU_SVC;
      svm_param.kernel_type = cv::SVM::RBF;
      svm_param.degree = 0.0;
      svm_param.gamma = 0.90;
      svm_param.coef0 = 0.50;
      svm_param.C = 1;
      svm_param.nu = 0.70;
      svm_param.p = 1.0;
      svm_param.class_weights = NULL;
      svm_param.term_crit.type = CV_TERMCRIT_ITER | CV_TERMCRIT_EPS;
      svm_param.term_crit.max_iter = 1e6;
      svm_param.term_crit.epsilon = 1e-6;
      cv::ParamGrid paramGrid = cv::ParamGrid();
      paramGrid.min_val = 0;
      paramGrid.max_val = 0;
      paramGrid.step = 1;

      /*this->supportVectorMachine_->train(
        featureMD, labelMD, cv::Mat(), cv::Mat(), svm_param);*/
      this->supportVectorMachine_->train_auto
         (featureMD, labelMD, cv::Mat(), cv::Mat(), svm_param, 10,
          paramGrid, cv::SVM::get_default_grid(cv::SVM::GAMMA),
          cv::SVM::get_default_grid(cv::SVM::P),
          cv::SVM::get_default_grid(cv::SVM::NU),
          cv::SVM::get_default_grid(cv::SVM::COEF),
          cv::SVM::get_default_grid(cv::SVM::DEGREE),
          true);
#endif
   }

   void SlidingWindowObjectDetectorTrainer::concatenateCVMat(
      const cv::Mat &mat_1, const cv::Mat &mat_2,
      cv::Mat &featureMD, bool iscolwise)
   {
      if (iscolwise) {
         featureMD = cv::Mat(mat_1.rows, (mat_1.cols + mat_2.cols), CV_32F);
         for (int i = 0; i < featureMD.rows; i++) {
            for (int j = 0; j < mat_1.cols; j++) {
               featureMD.at<float>(i, j) = mat_1.at<float>(i, j);
            }
            for (int j = mat_1.cols; j < featureMD.cols; j++) {
               featureMD.at<float>(i, j) = mat_2.at<float>(i, j - mat_1.cols);
            }
         }
      } else {
         featureMD = cv::Mat((mat_1.rows + mat_2.rows), mat_1.cols, CV_32F);
         for (int i = 0; i < featureMD.cols; i++) {
            for (int j = 0; j < mat_1.rows; j++) {
               featureMD.at<float>(j, i) = mat_1.at<float>(j, i);
            }
            for (int j = mat_1.rows; j < featureMD.rows; j++) {
               featureMD.at<float>(j, i) = mat_2.at<float>(j - mat_1.rows, i);
            }
         }
      }
   }

   void SlidingWindowObjectDetectorTrainer::writeTrainingManifestToDirectory(
      cv::FileStorage &fs)
   {
      fs <<  "TrainerInfo" << "{";
      fs << "trainer_type" << "cv::SVM";
      fs << "trainer_path" << this->trained_classifier_name_;
      fs << "}";

      fs <<  "FeatureInfo" << "{";
      fs << "HOG" << 1;
      fs << "COLOR_HISTOGRAM" << 1;
      fs << "}";

      fs <<  "SlidingWindowInfo" << "{";
      fs << "swindow_x" << this->swindow_x_;
      fs << "swindow_y" << this->swindow_y_;
      fs << "}";

      fs << "TrainingDatasetDirectoryInfo" << "{";
      fs << "object_dataset_filename" << this->object_dataset_filename_;
      fs << "nonobject_dataset_filename" << this->nonobject_dataset_filename_;
      fs << "dataset_path" << this->dataset_path_;  // only path to neg
      fs << "}";
   }

   void SlidingWindowObjectDetectorTrainer::computeHSHistogram(
      cv::Mat &src, cv::Mat &hist, const int hBin, const int sBin, bool is_norm)
   {
      if (src.empty()) {
         return;
      }
      cv::Mat hsv;
      cv::cvtColor(src, hsv, CV_BGR2HSV);
      int histSize[] = {hBin, sBin};
      float h_ranges[] = {0, 180};
      float s_ranges[] = {0, 256};
      const float* ranges[] = {h_ranges, s_ranges};
      int channels[] = {0, 1};
      cv::calcHist(
         &hsv, 1, channels, cv::Mat(), hist, 2, histSize, ranges, true, false);
      if (is_norm) {
         cv::normalize(hist, hist, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());
      }
   }
}  // namespace jsk_perception

int main(int argc, char *argv[]) {
    ros::init(argc, argv, "sliding_window_object_detector_trainer_node");
    ROS_INFO("RUNNING NODELET %s", "sliding_window_object_detector_trainer");
    jsk_perception::SlidingWindowObjectDetectorTrainer run_trainer;
    ros::spin();
    return 0;
}


double LevellerRasterBand::GetOffset(int* pbSuccess)
{
    LevellerDataset *poGDS = reinterpret_cast<LevellerDataset *>( poDS );
    if(pbSuccess != nullptr)
        *pbSuccess = TRUE;
    return poGDS->m_dElevBase;
}

/******************************************************************************
 * levellerdataset.cpp,v 1.22
 *
 * Project:  Leveller TER Driver
 * Purpose:  Reader for Leveller TER documents
 * Author:   Ray Gardener, Daylon Graphics Ltd.
 *
 * Portions of this module derived from GDAL drivers by
 * Frank Warmerdam, see http://www.gdal.org
 *
 ******************************************************************************
 *
 ****************************************************************************/

#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "ogr_spatialref.h"

CPL_CVSID("$Id: levellerdataset.cpp e46fca76df8aa6869375620579ff9cf3cff86e16 2018-04-26 22:56:23 +0200 Even Rouault $")

static bool str_equal(const char *_s1, const char *_s2) {
    return 0 == strcmp(_s1, _s2);
}

/*GDALDataset *LevellerCreateCopy( const char *, GDALDataset *, int, char **,
                                GDALProgressFunc pfnProgress,
                                void * pProgressData );
*/

/************************************************************************/
/* ==================================================================== */
/*                              LevellerDataset                         */
/* ==================================================================== */
/************************************************************************/

constexpr size_t kMaxTagNameLen = 63;

enum
{
    // Leveller coordsys types.
    LEV_COORDSYS_RASTER = 0,
    LEV_COORDSYS_LOCAL,
    LEV_COORDSYS_GEO
};

enum
{
    // Leveller digital axis extent styles.
    LEV_DA_POSITIONED = 0,
    LEV_DA_SIZED,
    LEV_DA_PIXEL_SIZED
};

typedef enum
{
    // Measurement unit IDs, OEM version.
    UNITLABEL_UNKNOWN   = 0x00000000,
    UNITLABEL_PIXEL     = 0x70780000,
    UNITLABEL_PERCENT   = 0x25000000,

    UNITLABEL_RADIAN    = 0x72616400,
    UNITLABEL_DEGREE    = 0x64656700,
    UNITLABEL_ARCMINUTE = 0x6172636D,
    UNITLABEL_ARCSECOND = 0x61726373,

    UNITLABEL_YM        = 0x796D0000,
    UNITLABEL_ZM        = 0x7A6D0000,
    UNITLABEL_AM        = 0x616D0000,
    UNITLABEL_FM        = 0x666D0000,
    UNITLABEL_PM        = 0x706D0000,
    UNITLABEL_A         = 0x41000000,
    UNITLABEL_NM        = 0x6E6D0000,
    UNITLABEL_U         = 0x75000000,
    UNITLABEL_UM        = 0x756D0000,
    UNITLABEL_PPT       = 0x70707400,
    UNITLABEL_PT        = 0x70740000,
    UNITLABEL_MM        = 0x6D6D0000,
    UNITLABEL_P         = 0x70000000,
    UNITLABEL_CM        = 0x636D0000,
    UNITLABEL_IN        = 0x696E0000,
    UNITLABEL_DFT       = 0x64667400,
    UNITLABEL_DM        = 0x646D0000,
    UNITLABEL_LI        = 0x6C690000,
    UNITLABEL_SLI       = 0x736C6900,
    UNITLABEL_SP        = 0x73700000,
    UNITLABEL_FT        = 0x66740000,
    UNITLABEL_SFT       = 0x73667400,
    UNITLABEL_YD        = 0x79640000,
    UNITLABEL_SYD       = 0x73796400,
    UNITLABEL_M         = 0x6D000000,
    UNITLABEL_FATH      = 0x66617468,
    UNITLABEL_R         = 0x72000000,
    UNITLABEL_RD        = UNITLABEL_R,
    UNITLABEL_DAM       = 0x64416D00,
    UNITLABEL_DKM       = UNITLABEL_DAM,
    UNITLABEL_CH        = 0x63680000,
    UNITLABEL_SCH       = 0x73636800,
    UNITLABEL_HM        = 0x686D0000,
    UNITLABEL_F         = 0x66000000,
    UNITLABEL_KM        = 0x6B6D0000,
    UNITLABEL_MI        = 0x6D690000,
    UNITLABEL_SMI       = 0x736D6900,
    UNITLABEL_NMI       = 0x6E6D6900,
    UNITLABEL_MEGAM     = 0x4D6D0000,
    UNITLABEL_LS        = 0x6C730000,
    UNITLABEL_GM        = 0x476D0000,
    UNITLABEL_LM        = 0x6C6D0000,
    UNITLABEL_AU        = 0x41550000,
    UNITLABEL_TM        = 0x546D0000,
    UNITLABEL_LHR       = 0x6C687200,
    UNITLABEL_LD        = 0x6C640000,
    UNITLABEL_PETAM     = 0x506D0000,
    UNITLABEL_LY        = 0x6C790000,
    UNITLABEL_PC        = 0x70630000,
    UNITLABEL_EXAM      = 0x456D0000,
    UNITLABEL_KLY       = 0x6B6C7900,
    UNITLABEL_KPC       = 0x6B706300,
    UNITLABEL_ZETTAM    = 0x5A6D0000,
    UNITLABEL_MLY       = 0x4D6C7900,
    UNITLABEL_MPC       = 0x4D706300,
    UNITLABEL_YOTTAM    = 0x596D0000
} UNITLABEL;

typedef struct
{
        const char* pszID;
        double      dScale;
        UNITLABEL   oemCode;
} measurement_unit;

constexpr double kdays_per_year = 365.25;
constexpr double kdLStoM = 299792458.0;
constexpr double kdLYtoM = kdLStoM * kdays_per_year * 24 * 60 * 60;
constexpr double kdInch = 0.3048 / 12;
constexpr double kPI = M_PI;

constexpr int kFirstLinearMeasureIdx = 9;

static const measurement_unit kUnits[] =
{
    { "", 1.0, UNITLABEL_UNKNOWN },
    { "px", 1.0, UNITLABEL_PIXEL },
    { "%", 1.0, UNITLABEL_PERCENT }, // not actually used

    { "rad", 1.0, UNITLABEL_RADIAN },
    { "\xB0", kPI / 180.0, UNITLABEL_DEGREE }, // \xB0 is Unicode degree symbol
    { "d", kPI / 180.0, UNITLABEL_DEGREE },
    { "deg", kPI / 180.0, UNITLABEL_DEGREE },
    { "'", kPI / (60.0 * 180.0), UNITLABEL_ARCMINUTE },
    { "\"", kPI / (3600.0 * 180.0), UNITLABEL_ARCSECOND },

    { "ym", 1.0e-24, UNITLABEL_YM },
    { "zm", 1.0e-21, UNITLABEL_ZM },
    { "am", 1.0e-18, UNITLABEL_AM },
    { "fm", 1.0e-15, UNITLABEL_FM },
    { "pm", 1.0e-12, UNITLABEL_PM },
    { "A",  1.0e-10, UNITLABEL_A },
    { "nm", 1.0e-9, UNITLABEL_NM },
    { "u",  1.0e-6, UNITLABEL_U },
    { "um", 1.0e-6, UNITLABEL_UM },
    { "ppt", kdInch / 72.27, UNITLABEL_PPT },
    { "pt", kdInch / 72.0, UNITLABEL_PT },
    { "mm", 1.0e-3, UNITLABEL_MM },
    { "p", kdInch / 6.0, UNITLABEL_P },
    { "cm", 1.0e-2, UNITLABEL_CM },
    { "in", kdInch, UNITLABEL_IN },
    { "dft", 0.03048, UNITLABEL_DFT },
    { "dm", 0.1, UNITLABEL_DM },
    { "li", 0.2011684 /* GDAL 0.20116684023368047 ? */, UNITLABEL_LI },
    { "sli", 0.201168402336805, UNITLABEL_SLI },
    { "sp", 0.2286, UNITLABEL_SP },
    { "ft", 0.3048, UNITLABEL_FT },
    { "sft", 1200.0 / 3937.0, UNITLABEL_SFT },
    { "yd", 0.9144, UNITLABEL_YD },
    { "syd", 0.914401828803658, UNITLABEL_SYD },
    { "m", 1.0, UNITLABEL_M },
    { "fath", 1.8288, UNITLABEL_FATH },
    { "rd", 5.02921, UNITLABEL_RD },
    { "dam", 10.0, UNITLABEL_DAM },
    { "dkm", 10.0, UNITLABEL_DKM },
    { "ch", 20.1168 /* GDAL: 2.0116684023368047 ? */, UNITLABEL_CH },
    { "sch", 20.1168402336805, UNITLABEL_SCH },
    { "hm", 100.0, UNITLABEL_HM },
    { "f", 201.168, UNITLABEL_F },
    { "km", 1000.0, UNITLABEL_KM },
    { "mi", 1609.344, UNITLABEL_MI },
    { "smi", 1609.34721869444, UNITLABEL_SMI },
    { "nmi", 1853.0, UNITLABEL_NMI },
    { "Mm", 1.0e+6, UNITLABEL_MEGAM },
    { "ls", kdLStoM, UNITLABEL_LS },
    { "Gm", 1.0e+9, UNITLABEL_GM },
    { "lm", kdLStoM * 60, UNITLABEL_LM },
    { "AU", 8.317 * kdLStoM * 60, UNITLABEL_AU },
    { "Tm", 1.0e+12, UNITLABEL_TM },
    { "lhr", 60.0 * 60.0 * kdLStoM, UNITLABEL_LHR },
    { "ld", 24 * 60.0 * 60.0 * kdLStoM, UNITLABEL_LD },
    { "Pm", 1.0e+15, UNITLABEL_PETAM },
    { "ly", kdLYtoM, UNITLABEL_LY },
    { "pc", 3.2616 * kdLYtoM, UNITLABEL_PC },
    { "Em", 1.0e+18, UNITLABEL_EXAM },
    { "kly", 1.0e+3 * kdLYtoM, UNITLABEL_KLY },
    { "kpc", 3.2616 * 1.0e+3 * kdLYtoM, UNITLABEL_KPC },
    { "Zm", 1.0e+21, UNITLABEL_ZETTAM },
    { "Mly", 1.0e+6 * kdLYtoM, UNITLABEL_MLY },
    { "Mpc", 3.2616 * 1.0e+6 * kdLYtoM, UNITLABEL_MPC },
    { "Ym", 1.0e+24, UNITLABEL_YOTTAM }
};

static bool approx_equal(double a, double b)
{
    const double epsilon = 1e-5;
    return fabs(a-b) <= epsilon;
}

class LevellerRasterBand;

class LevellerDataset final: public GDALPamDataset
{
    friend class LevellerRasterBand;
    friend class digital_axis;

    int                 m_version;

    char*               m_pszFilename;
    char*               m_pszProjection;

    char                m_szElevUnits[8];
    double              m_dElevScale;  // physical-to-logical scaling.
    double              m_dElevBase;   // logical offset.
    double              m_adfTransform[6];
    double              m_dLogSpan[2];

    VSILFILE*           m_fp;
    vsi_l_offset        m_nDataOffset;

    bool load_from_file(VSILFILE*, const char*);

    static bool locate_data(vsi_l_offset&, size_t&, VSILFILE*, const char*);
    bool get(int&, VSILFILE*, const char*);
    bool get(size_t& n, VSILFILE* fp, const char* psz)
        { return this->get((int&)n, fp, psz); }
    bool get(double&, VSILFILE*, const char*);
    bool get(char*, size_t, VSILFILE*, const char*);

    bool write_header();
    bool write_tag(const char*, int);
    bool write_tag(const char*, size_t);
    bool write_tag(const char*, double);
    bool write_tag(const char*, const char*);
    bool write_tag_start(const char*, size_t);
    bool write(int);
    bool write(size_t);
    bool write(double);
    bool write_byte(size_t);

    static const measurement_unit* get_uom(const char*);
    static const measurement_unit* get_uom(UNITLABEL);
    static const measurement_unit* get_uom(double);

    static bool convert_measure(double, double&, const char* pszUnitsFrom);
    bool make_local_coordsys(const char* pszName, const char* pszUnits);
    bool make_local_coordsys(const char* pszName, UNITLABEL);
    const char* code_to_id(UNITLABEL) const;
    UNITLABEL id_to_code(const char*) const;
    UNITLABEL meter_measure_to_code(double) const;
    bool compute_elev_scaling(const OGRSpatialReference&);
    void raw_to_proj(double, double, double&, double&) const;

public:
    LevellerDataset();
    virtual ~LevellerDataset();

    static GDALDataset* Open( GDALOpenInfo* );
    static int Identify( GDALOpenInfo* );
    static GDALDataset* Create( const char* pszFilename,
                                int nXSize, int nYSize, int nBands,
                                GDALDataType eType, char** papszOptions );

    virtual CPLErr      GetGeoTransform( double* ) override;
    virtual const char* GetProjectionRef(void) override;

    virtual CPLErr      SetGeoTransform( double* ) override;
    virtual CPLErr      SetProjection(const char*) override;
};

class digital_axis
{
 public:
    digital_axis() : m_eStyle(LEV_DA_PIXEL_SIZED), m_fixedEnd(0)
    {
        m_d[0] = 0.0;
        m_d[1] = 0.0;
    }

    bool get(LevellerDataset& ds, VSILFILE* fp, int n)
    {
        char szTag[32];
        snprintf(szTag, sizeof(szTag), "coordsys_da%d_style", n);
        if(!ds.get(m_eStyle, fp, szTag))
            return false;
        snprintf(szTag, sizeof(szTag), "coordsys_da%d_fixedend", n);
        if(!ds.get(m_fixedEnd, fp, szTag))
            return false;
        snprintf(szTag, sizeof(szTag), "coordsys_da%d_v0", n);
        if(!ds.get(m_d[0], fp, szTag))
            return false;
        snprintf(szTag, sizeof(szTag), "coordsys_da%d_v1", n);
        if(!ds.get(m_d[1], fp, szTag))
            return false;
        return true;
    }

    double origin(size_t pixels) const
    {
        if(m_fixedEnd == 1)
        {
            switch(m_eStyle)
            {
            case LEV_DA_SIZED:
                return m_d[1] + m_d[0];

            case LEV_DA_PIXEL_SIZED:
                return m_d[1] + (m_d[0] * (pixels-1));
            }
        }
        return m_d[0];
    }

    double scaling(size_t pixels) const
    {
        CPLAssert(pixels > 1);
        if(m_eStyle == LEV_DA_PIXEL_SIZED)
            return m_d[1 - m_fixedEnd];

        return this->length(static_cast<int>(pixels)) / (pixels - 1);
    }

    double length(int pixels) const
    {
        // Return the signed length of the axis.

        switch(m_eStyle)
        {
        case LEV_DA_POSITIONED:
            return m_d[1] - m_d[0];

        case LEV_DA_SIZED:
            return m_d[1 - m_fixedEnd];

        case LEV_DA_PIXEL_SIZED:
            return m_d[1 - m_fixedEnd] * (pixels-1);
        }
        CPLAssert(false);
        return 0.0;
    }

 protected:
    int m_eStyle;
    size_t m_fixedEnd;
    double m_d[2];
};

/************************************************************************/
/* ==================================================================== */
/*                            LevellerRasterBand                        */
/* ==================================================================== */
/************************************************************************/

class LevellerRasterBand final: public GDALPamRasterBand
{
    friend class LevellerDataset;

    float*      m_pLine;
    bool        m_bFirstTime;

public:

    explicit LevellerRasterBand(LevellerDataset*);
    virtual ~LevellerRasterBand();

    bool        Init();

    // Geomeasure support.
    virtual const char* GetUnitType() override;
    virtual double GetScale(int* pbSuccess = nullptr) override;
    virtual double GetOffset(int* pbSuccess = nullptr) override;

    virtual CPLErr IReadBlock( int, int, void * ) override;
    virtual CPLErr IWriteBlock( int, int, void * ) override;
    virtual CPLErr SetUnitType( const char* ) override;
};

/************************************************************************/
/*                         LevellerRasterBand()                         */
/************************************************************************/

LevellerRasterBand::LevellerRasterBand( LevellerDataset *poDSIn ) :
    m_pLine(nullptr),
    m_bFirstTime(true)
{
    poDS = poDSIn;
    nBand = 1;

    eDataType = GDT_Float32;

    nBlockXSize = poDS->GetRasterXSize();
    nBlockYSize = 1; // poDS->GetRasterYSize();
}

/************************************************************************/
/*                           Init()                                     */
/************************************************************************/

bool LevellerRasterBand::Init()
{
    m_pLine = reinterpret_cast<float*>(
        VSI_MALLOC2_VERBOSE( sizeof(float), nBlockXSize) );
    return m_pLine != nullptr;
}

LevellerRasterBand::~LevellerRasterBand()
{
    CPLFree(m_pLine);
}

/************************************************************************/
/*                             IWriteBlock()                            */
/************************************************************************/

CPLErr LevellerRasterBand::IWriteBlock
(
    CPL_UNUSED int nBlockXOff,
    int nBlockYOff,
    void* pImage
)
{
    CPLAssert( nBlockXOff == 0  );
    CPLAssert( pImage != nullptr );
    CPLAssert( m_pLine != nullptr );

/*  #define sgn(_n) ((_n) < 0 ? -1 : ((_n) > 0 ? 1 : 0) )
    #define sround(_f)                          \
    (int)((_f) + (0.5 * sgn(_f)))
*/
    const size_t pixelsize = sizeof(float);

    LevellerDataset& ds = *reinterpret_cast<LevellerDataset*>( poDS );
    if(m_bFirstTime)
    {
        m_bFirstTime = false;
        if(!ds.write_header())
            return CE_Failure;
        ds.m_nDataOffset = VSIFTellL(ds.m_fp);
    }
    const size_t rowbytes = nBlockXSize * pixelsize;
    const float* pfImage = reinterpret_cast<float *>( pImage );

    if(0 == VSIFSeekL(
           ds.m_fp, ds.m_nDataOffset + nBlockYOff * rowbytes,
               SEEK_SET))
    {
        for(size_t x = 0; x < (size_t)nBlockXSize; x++)
        {
            // Convert logical elevations to physical.
            m_pLine[x] = static_cast<float>(
                (pfImage[x] - ds.m_dElevBase) / ds.m_dElevScale );
        }

#ifdef CPL_MSB
        GDALSwapWords( m_pLine, pixelsize, nBlockXSize, pixelsize );
#endif
        if(1 == VSIFWriteL(m_pLine, rowbytes, 1, ds.m_fp))
            return CE_None;
    }

    return CE_Failure;
}

CPLErr LevellerRasterBand::SetUnitType( const char* psz )
{
    LevellerDataset& ds = *reinterpret_cast<LevellerDataset *>( poDS );

    if(strlen(psz) >= sizeof(ds.m_szElevUnits))
        return CE_Failure;

    strcpy(ds.m_szElevUnits, psz);

    return CE_None;
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr LevellerRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff,
                                       int nBlockYOff,
                                       void* pImage )

{
    CPLAssert( sizeof(float) == sizeof(GInt32) );
    CPLAssert( nBlockXOff == 0  );
    CPLAssert( pImage != nullptr );

    LevellerDataset *poGDS = reinterpret_cast<LevellerDataset *>( poDS );

/* -------------------------------------------------------------------- */
/*      Seek to scanline.                                               */
/* -------------------------------------------------------------------- */
    const size_t rowbytes = nBlockXSize * sizeof(float);

    if(0 != VSIFSeekL(
           poGDS->m_fp,
           poGDS->m_nDataOffset + nBlockYOff * rowbytes,
           SEEK_SET))
    {
        CPLError( CE_Failure, CPLE_FileIO,
                  "Leveller seek failed: %s", VSIStrerror( errno ) );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Read the scanline into the image buffer.                        */
/* -------------------------------------------------------------------- */

    if( VSIFReadL( pImage, rowbytes, 1, poGDS->m_fp ) != 1 )
    {
        CPLError( CE_Failure, CPLE_FileIO,
                  "Leveller read failed: %s", VSIStrerror( errno ) );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Swap on MSB platforms.                                          */
/* -------------------------------------------------------------------- */
#ifdef CPL_MSB
    GDALSwapWords( pImage, 4, nRasterXSize, 4 );
#endif

/* -------------------------------------------------------------------- */
/*      Convert from legacy-format fixed-point if necessary.            */
/* -------------------------------------------------------------------- */
    float* pf = reinterpret_cast<float *>( pImage );

    if(poGDS->m_version < 6)
    {
        GInt32* pi = reinterpret_cast<int *>( pImage );
        for(size_t i = 0; i < (size_t)nBlockXSize; i++)
            pf[i] = static_cast<float>( pi[i] ) / 65536;
    }

/* -------------------------------------------------------------------- */
/*      Convert raw elevations to realworld elevs.                      */
/* -------------------------------------------------------------------- */
#if 0
    for(size_t i = 0; i < nBlockXSize; i++)
        pf[i] *= poGDS->m_dWorldscale; //this->GetScale();
#endif

    return CE_None;
}

/************************************************************************/
/*                            GetUnitType()                             */
/************************************************************************/
const char *LevellerRasterBand::GetUnitType()
{
    // Return elevation units.

    LevellerDataset *poGDS = reinterpret_cast<LevellerDataset *>( poDS );

    return poGDS->m_szElevUnits;
}

/************************************************************************/
/*                              GetScale()                              */
/************************************************************************/

double LevellerRasterBand::GetScale(int* pbSuccess)
{
    LevellerDataset *poGDS = reinterpret_cast<LevellerDataset *>( poDS );
    if(pbSuccess != nullptr)
        *pbSuccess = TRUE;
    return poGDS->m_dElevScale;
}

/************************************************************************/
/*                             GetOffset()                              */
/************************************************************************/


bool Hero::PlungingState::can_start_gameover_sequence() const {
  return false;
}

/*
 *
 */
#include "solarus/audio/Sound.h"
#include "solarus/hero/BackToSolidGroundState.h"
#include "solarus/hero/FreeState.h"
#include "solarus/hero/HeroSprites.h"
#include "solarus/hero/PlungingState.h"
#include "solarus/hero/SwimmingState.h"
#include "solarus/core/Equipment.h"
#include "solarus/core/Game.h"

namespace Solarus {

/**
 * \brief Constructor.
 * \param hero The hero controlled by this state.
 */
Hero::PlungingState::PlungingState(Hero& hero):
  HeroState(hero, "plunging") {

}

/**
 * \brief Starts this state.
 * \param previous_state the previous state
 */
void Hero::PlungingState::start(const State* previous_state) {

  HeroState::start(previous_state);

  if (get_entity().get_ground_below() == Ground::DEEP_WATER) {
    get_sprites().set_animation("plunging_water");
  }
  else {
    get_sprites().set_animation("plunging_lava");
  }
  Sound::play("splash");
}

/**
 * \brief Updates this state.
 */
void Hero::PlungingState::update() {

  HeroState::update();

  if (get_sprites().is_animation_finished()) {

    Hero& hero = get_entity();
    int drown = 0;
    if (hero.get_ground_below() == Ground::DEEP_WATER) {

      if (get_equipment().has_ability(Ability::SWIM)) {
        hero.set_state(new SwimmingState(hero));
      }
      else {
        drown = 1;
      }
    }
    else if (hero.get_ground_below() == Ground::LAVA) {
      drown = 4;
    }
    else {
      hero.set_state(new FreeState(hero));
    }

    if (drown > 0) {
      get_equipment().remove_life(drown);
      hero.set_state(new BackToSolidGroundState(hero, true, 300));
    }
  }
}

/**
 * \brief Returns whether the game over sequence can start in the current state.
 * \return true if the game over sequence can start in the current state
 */

/**
 * \brief Returns whether the hero is touching the ground in the current state.
 * \return true if the hero is touching the ground in the current state
 */
bool Hero::PlungingState::is_touching_ground() const {
  return false;
}

}