/********************************************************/
/* Routine de lecture d'un fichier GERBER format RS274X */
/********************************************************/

#include "fctsys.h"

#include "common.h"
#include "gerbview.h"
#include "pcbplot.h"

#include "protos.h"

#define IsNumber( x ) ( ( ( (x) >= '0' ) && ( (x) <='9' ) )   \
                       || ( (x) == '-' ) || ( (x) == '+' ) || ( (x) == '.' ) || ( (x) == ',' ) )

#define CODE( x, y ) ( (x << 8) + (y) )

enum rs274x_parameters {
    FORMAT_STATEMENT_COMMAND = CODE( 'F', 'S' ),
    AXIS_SELECT   = CODE( 'A', 'S' ),
    MIRROR_IMAGE  = CODE( 'M', 'I' ),
    MODE_OF_UNITS = CODE( 'M', 'O' ),
    INCH   = CODE( 'I', 'N' ),
    MILLIMETER = CODE( 'M', 'M' ),
    OFFSET = CODE( 'O', 'F' ),
    SCALE_FACTOR   = CODE( 'S', 'F' ),

    IMAGE_NAME = CODE( 'I', 'N' ),
    IMAGE_JUSTIFY  = CODE( 'I', 'J' ),
    IMAGE_OFFSET   = CODE( 'I', 'O' ),
    IMAGE_POLARITY = CODE( 'I', 'P' ),
    IMAGE_ROTATION = CODE( 'I', 'R' ),
    PLOTTER_FILM    = CODE( 'P', 'M' ),
    INCLUDE_FILE    = CODE( 'I', 'F' ),

    APERTURE_DESCR  = CODE( 'A', 'D' ),
    APERTURE_MACRO  = CODE( 'A', 'M' ),
    LAYER_NAME = CODE( 'L', 'N' ),
    LAYER_POLARITY  = CODE( 'L', 'P' ),
    KNOCKOUT = CODE( 'K', 'O' ),
    STEP_AND_REPEAT = CODE( 'S', 'P' ),
    ROTATE = CODE( 'R', 'O' )
};

/* Variables locales : */

/* Routines Locales */
static bool ReadApertureMacro( char* buff, char*& text, FILE* gerber_file );

/* Lit 2 codes ascii du texte pointé par text
 *  retourne le code correspondant ou -1 si erreur
 */
static int ReadXCommand( char*& text )
{
    int result;

    if( text && *text )
    {
        result = (*text) << 8; text++;
    }
    else
        return -1;
    if( text && *text )
    {
        result += (*text) & 255; text++;
    }
    else
        return -1;
    return result;
}


/********************************/
static int ReadInt( char*& text )
/********************************/
{
    int nb = 0;

    while( text && *text == ' ' )
        text++;

    // Skip blanks before number

    while( text && *text )
    {
        if( (*text >= '0') && (*text <='9') )
        {
            nb *= 10; nb += *text & 0x0F;
            text++;
        }
        else
            break;
    }

    return nb;
}


/************************************/
static double ReadDouble( char*& text )
/************************************/
{
    double nb = 0.0;
    char   buf[256], * ptchar;

    ptchar = buf;
    while( text && *text == ' ' )
        text++;

    // Skip blanks before number
    while( text && *text )
    {
        if( IsNumber( *text ) )
        {
            *ptchar = *text;
            text++; ptchar++;
        }
        else
            break;
    }

    *ptchar = 0;

    nb = atof( buf );
    return nb;
}


/****************************************************************************/
bool GERBER_Descr::ReadRS274XCommand( WinEDA_GerberFrame* frame, wxDC* DC,
                                      char* buff, char*& text )
/****************************************************************************/

/* Lit toutes les commandes RS274X jusqu'a trouver de code de fin %
 *  appelle ExecuteRS274XCommand() pour chaque commande trouvée
 */
{
    bool ok = true;
    int  code_command;

    text++;

    for(;;)
    {
        while( *text )
        {
            switch( *text )
            {
            case '%':       // End commande
                text++;
                m_CommandState = CMD_IDLE;
                goto exit;  // success completion

            case ' ':
            case '\r':
            case '\n':
                text++;
                break;

            case '*':
                text++;
                break;

            default:
                code_command = ReadXCommand( text );
                ok = ExecuteRS274XCommand( code_command, buff, text );
                if( !ok )
                    goto exit;
                break;
            }
        }

        // End of current line
        if( fgets( buff, 255, m_Current_File ) == NULL )
        {
            ok = false;
            break;
        }
        
        text = buff;
    }

exit:
    return ok;
}


/*******************************************************************************/
bool GERBER_Descr::ExecuteRS274XCommand( int command, char* buff, char*& text )
/*******************************************************************************/

/* Execute 1 commande RS274X
 */
{
    int      code;
    int      xy_seq_len, xy_seq_char;
    char     ctmp;
    bool     ok = TRUE;
    D_CODE*  dcode;
    char     Line[1024];
    wxString msg;
    double   fcoord;
    double   conv_scale = m_GerbMetric ? PCB_INTERNAL_UNIT / 25.4 : PCB_INTERNAL_UNIT;


    switch( command )
    {
    case FORMAT_STATEMENT_COMMAND:
        xy_seq_len = 2;
        
        while( *text != '*' )
        {
            switch( *text )
            {
            case ' ':
                text++;
                break;

            case 'L':       // No Leading 0
                m_NoTrailingZeros = FALSE;
                text++;
                break;

            case 'T':       // No trailing 0
                m_NoTrailingZeros = TRUE;
                text++;
                break;

            case 'A':           // Absolute coord
                m_Relative = FALSE; text++;
                break;

            case 'I':           // Absolute coord
                m_Relative = TRUE; text++;
                break;

            case 'N':           // Sequence code (followed by the number of digits for the X,Y command
                text++;
                xy_seq_char = *text; text++;
                if( (xy_seq_char >= '0') && (xy_seq_char <= '9') )
                    xy_seq_len = -'0';
                break;

            case 'X':
            case 'Y':           // Valeurs transmises :2 (really xy_seq_len : FIX ME) digits
                code = *(text++); ctmp = *(text++) - '0';
                if( code == 'X' )
                {
                    m_FmtScale.x = *text - '0';                 // = nb chiffres apres la virgule
                    m_FmtLen.x   = ctmp + m_FmtScale.x;         // = nb total de chiffres
                }
                else
                {
                    m_FmtScale.y = *text - '0';
                    m_FmtLen.y   = ctmp + m_FmtScale.y;
                }
                text++;
                break;

            case '*':
                break;

            default:
                GetEndOfBlock( buff, text, m_Current_File );
                ok = FALSE;
                break;
            }
        }

        break;

    case AXIS_SELECT:
    case MIRROR_IMAGE:
        ok = FALSE;
        break;

    case MODE_OF_UNITS:
        code = ReadXCommand( text );
        if( code == INCH )
            m_GerbMetric = FALSE;
        else if( code == MILLIMETER )
            m_GerbMetric = TRUE;
        conv_scale = m_GerbMetric ? PCB_INTERNAL_UNIT / 25.4 : PCB_INTERNAL_UNIT;
        break;

    case OFFSET:        // command: OFAnnBnn (nn = float number)
        m_Offset.x = m_Offset.y = 0;
        while( *text != '*' )
        {
            switch( *text )
            {
            case 'A':       // A axis offset in current unit (inch ou mm)
                text++;
                fcoord     = ReadDouble( text );
                m_Offset.x = (int) round( fcoord * conv_scale );
                break;

            case 'B':       // B axis offset in current unit (inch ou mm)
                text++;
                fcoord     = ReadDouble( text );
                m_Offset.y = (int) round( fcoord * conv_scale );
                break;
            }
        }

        break;

    case SCALE_FACTOR:
    case IMAGE_JUSTIFY:
    case IMAGE_ROTATION:
    case IMAGE_OFFSET:
    case PLOTTER_FILM:
    case LAYER_NAME:
    case KNOCKOUT:
    case STEP_AND_REPEAT:
    case ROTATE:
        msg.Printf( _( "Command <%c%c> ignored by Gerbview" ),
                    (command >> 8) & 0xFF, command & 0xFF );
        if( g_DebugLevel > 0 )
            wxMessageBox( msg );
        break;

    case IMAGE_NAME:
        m_Name.Empty();
        while( *text != '*' )
        {
            m_Name.Append( *text ); text++;
        }

        break;

    case IMAGE_POLARITY:
        if( strnicmp( text, "NEG", 3 ) == 0 )
            m_ImageNegative = TRUE;
        else
            m_ImageNegative = FALSE;
        break;

    case LAYER_POLARITY:
        if( *text == 'C' )
            m_LayerNegative = TRUE;
        else
            m_LayerNegative = FALSE;
        break;

    case APERTURE_MACRO:
        ReadApertureMacro( buff, text, m_Current_File );
        break;

    case INCLUDE_FILE:
        if( m_FilesPtr >= 10 )
        {
            ok = FALSE;
            DisplayError( NULL, _( "Too many include files!!" ) );
            break;
        }
        strcpy( Line, text );
        strtok( Line, "*%%\n\r" );
        m_FilesList[m_FilesPtr] = m_Current_File;
        m_Current_File = fopen( Line, "rt" );
        if( m_Current_File == 0 )
        {
            wxString msg;
            msg.Printf( wxT( "fichier <%s> non trouve" ), Line );
            DisplayError( NULL, msg, 10 );
            ok = FALSE;
            m_Current_File = m_FilesList[m_FilesPtr];
            break;
        }
        m_FilesPtr++;
        break;

    case APERTURE_DESCR:
        if( *text != 'D' )
        {
            ok = FALSE; break;
        }
        m_As_DCode = TRUE;
        text++;
        code  = ReadInt( text );
        ctmp  = *text;
        dcode = ReturnToolDescr( m_Layer, code );
        if( dcode == NULL )
            break;
        if( text[1] == ',' )        // Tool usuel (C,R,O,P)
        {
            text += 2;              // text pointe size ( 1er modifier)
            dcode->m_Size.x = dcode->m_Size.y =
                                  (int) round( ReadDouble( text ) * conv_scale );

            switch( ctmp )
            {
            case 'C':               // Circle
                dcode->m_Shape = GERB_CIRCLE;
                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
                    dcode->m_Drill.x = dcode->m_Drill.y =
                                           (int) round( ReadDouble( text ) * conv_scale );
                    dcode->m_DrillShape = 1;
                }
                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
                    dcode->m_Drill.y =
                        (int) round( ReadDouble( text ) * conv_scale );
                    dcode->m_DrillShape = 2;
                }
                dcode->m_Defined = TRUE;
                break;

            case 'O':               // ovale
            case 'R':               // rect
                dcode->m_Shape = (ctmp == 'O') ? GERB_OVALE : GERB_RECT;
                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
                    dcode->m_Size.y =
                        (int) round( ReadDouble( text ) * conv_scale );
                }
                while( *text == ' ' )
                    text++;

                if( *text == 'X' )
                {
                    text++;
                    dcode->m_Drill.x = dcode->m_Drill.y =
                                           (int) round( ReadDouble( text ) * conv_scale );
                    dcode->m_DrillShape = 1;
                }
                while( *text == ' ' )
                    text++;

                if( *text == 'Y' )
                {
                    text++;
                    dcode->m_Drill.y =
                        (int) round( ReadDouble( text ) * conv_scale );
                    dcode->m_DrillShape = 2;
                }
                dcode->m_Defined = TRUE;
                break;

            case 'P':               // Polygone
// A modifier: temporairement la forme ronde est utilisée
                dcode->m_Shape   = GERB_CIRCLE;
                dcode->m_Defined = TRUE;
                break;
            }
        }
        break;

    default:
        ok = FALSE;
        break;
    }

    ok = GetEndOfBlock( buff, text, m_Current_File );
    
    return ok;
}


/*****************************************************************/
bool GetEndOfBlock( char* buff, char*& text, FILE* gerber_file )
/*****************************************************************/
{
    for(;;)
    {
        while( (text < buff + 255) && *text )
        {
            if( *text == '*' )
                return TRUE;
            if( *text == '%' )
                return TRUE;
            text++;
        }

        if( fgets( buff, 255, gerber_file ) == NULL )
            break;
        
        text = buff;
    }

    return FALSE;
}


/*******************************************************************/
bool ReadApertureMacro( char* buff, char*& text, FILE* gerber_file )
/*******************************************************************/
{
    wxString macro_name;
    int      macro_type = 0;

    // Read macro name
    while( (text < buff + 255) && *text )
    {
        if( *text == '*' )
            break;
        macro_name.Append( *text );
        text++;
    }

    if( g_DebugLevel > 0 )
        wxMessageBox( macro_name, wxT( "macro name" ) );
    text = buff;
    fgets( buff, 255, gerber_file );

    // Read parameters
    macro_type = ReadInt( text );

    while( (text < buff + 255) && *text )
    {
        if( *text == '*' )
            return TRUE;
        text++;
    }

    return FALSE;
}
