/*******************************************************************************/
/*	library editor: edition of fields of lib entries (components in libraries) */
/*******************************************************************************/

#include <algorithm>

#include "fctsys.h"
#include "common.h"
#include "confirm.h"
#include "class_drawpanel.h"

#include "program.h"
#include "general.h"
#include "protos.h"
#include "libeditframe.h"
#include "class_library.h"

#include "dialog_edit_libentry_fields_in_lib_base.h"

// Local variables:
static int s_SelectedRow;

#define COLUMN_FIELD_NAME 0
#define COLUMN_TEXT 1

/*****************************************************************************************/
class DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB : public DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB_BASE
/*****************************************************************************************/
{
private:
    WinEDA_LibeditFrame*    m_Parent;
    LIB_COMPONENT* m_LibEntry;
    bool m_skipCopyFromPanel;

    /// a copy of the edited component's LIB_FIELDs
    std::vector <LIB_FIELD> m_FieldsBuf;

public:
    DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB( WinEDA_LibeditFrame* aParent,
                                        LIB_COMPONENT* aLibEntry );
    ~DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB();

private:

    // Events handlers:
    void OnInitDialog( wxInitDialogEvent& event );

    void OnListItemDeselected( wxListEvent& event );
    void OnListItemSelected( wxListEvent& event );
    void addFieldButtonHandler( wxCommandEvent& event );
    void deleteFieldButtonHandler( wxCommandEvent& event );
    void moveUpButtonHandler( wxCommandEvent& event );
    void OnCancelButtonClick( wxCommandEvent& event );
    void OnOKButtonClick( wxCommandEvent& event );

    // internal functions:
    void setSelectedFieldNdx( int aFieldNdx );

    int  getSelectedFieldNdx();

    /**
     * Function InitBuffers
     * sets up to edit the given component.
     * @param aComponent The component to edit.
     */
    void InitBuffers( void );

    /**
     * Function copySelectedFieldToPanel
     * sets the values displayed on the panel according to
     * the currently selected field row
     */
    void copySelectedFieldToPanel();

    /**
     * Function copyPanelToSelectedField
     * copies the values displayed on the panel fields to the currently selected field
     * @return bool - true if all fields are OK, else false if the user has put
     *   bad data into a field, and this value can be used to deny a row change.
     */
    bool copyPanelToSelectedField();
    void setRowItem( int aFieldNdx, const LIB_FIELD& aField );

    /** Function updateDisplay
     * update the listbox showing fields, according to the fields texts
     * must be called after a text change in fields, if this change is not an edition
     */
    void updateDisplay( )
    {
        for( unsigned ii = FIELD1;  ii<m_FieldsBuf.size(); ii++ )
            setRowItem( ii, m_FieldsBuf[ii] );
    }

    /** Function reinitializeFieldsIdAndDefaultNames
     * Calculates  the field id and default name, after deleting a field
     * or moving a field
    */
    void reinitializeFieldsIdAndDefaultNames();
};


void WinEDA_LibeditFrame::InstallFieldsEditorDialog( wxCommandEvent& event )
{
    if( m_component == NULL )
        return;

    DrawPanel->UnManageCursor( 0, wxCURSOR_ARROW );

    DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB dlg( this, m_component );

    int abort = dlg.ShowModal();

    if( abort )
        return;

    UpdateAliasSelectList();
    UpdatePartSelectList();
    DisplayLibInfos();
    Refresh();
}


/***********************************************************************/
DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB(
    WinEDA_LibeditFrame* aParent,
    LIB_COMPONENT*       aLibEntry ) :
    DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB_BASE( aParent )
/***********************************************************************/
{
    m_Parent   = aParent;
    m_LibEntry = aLibEntry;
}


/***********************************************************************/
DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::~DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB()
/***********************************************************************/
{
}


/**********************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::OnInitDialog( wxInitDialogEvent& event )
/**********************************************************************************/
{
    m_skipCopyFromPanel = false;
    wxListItem columnLabel;

    columnLabel.SetImage( -1 );

    columnLabel.SetText( _( "Name" ) );
    fieldListCtrl->InsertColumn( COLUMN_FIELD_NAME, columnLabel );

    columnLabel.SetText( _( "Value" ) );
    fieldListCtrl->InsertColumn( COLUMN_TEXT, columnLabel );

    wxString label = _( "Size" ) + ReturnUnitSymbol( g_UnitMetric );
    textSizeLabel->SetLabel( label );

    label  = _( "Pos " );
    label += _( "X" );
    label += ReturnUnitSymbol( g_UnitMetric );
    posXLabel->SetLabel( label );

    label  = _( "Pos " );
    label += _( "Y" );
    label += ReturnUnitSymbol( g_UnitMetric );
    posYLabel->SetLabel( label );

    InitBuffers();
    copySelectedFieldToPanel();

    if( GetSizer() )
    {
        GetSizer()->SetSizeHints( this );
    }
}


/**********************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::OnListItemDeselected( wxListEvent& event )
/**********************************************************************************/
{
    if( !m_skipCopyFromPanel )
    {
        if( !copyPanelToSelectedField() )
            event.Skip();   // do not go to the next row
    }
}


/**********************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::OnListItemSelected( wxListEvent& event )
/**********************************************************************************/
{
    // remember the selected row, statically
    s_SelectedRow = event.GetIndex();

    copySelectedFieldToPanel();
}


/***********************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::OnCancelButtonClick( wxCommandEvent& event )
/***********************************************************************************/
{
    EndModal( 1 );
}


/**********************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::OnOKButtonClick( wxCommandEvent& event )
/**********************************************************************************/
{
    if( !copyPanelToSelectedField() )
        return;

    /* A new name could be entered in VALUE field.
     *  Must not be an existing alias name in alias list box */
    wxString* newvalue = &m_FieldsBuf[VALUE].m_Text;
    for( size_t i = 0; i < m_LibEntry->m_AliasList.GetCount(); i++ )
    {
        if( newvalue->CmpNoCase( m_LibEntry->m_AliasList[i] ) == 0 )
        {
            wxString msg;
            msg.Printf( _( "A new name is entered for this component\nAn \
alias %s already exists!\nCannot update this component" ),
                        newvalue->GetData() );
            DisplayError( this, msg );
            return;
        }
    }

    /* save old cmp in undo list */
    m_Parent->SaveCopyInUndoList( m_LibEntry, IS_CHANGED );

    // delete any fields with no name
    for( unsigned i = FIELD1; i < m_FieldsBuf.size(); )
    {
        if( m_FieldsBuf[i].m_Name.IsEmpty() )
        {
            m_FieldsBuf.erase( m_FieldsBuf.begin() + i );
            continue;
        }

        ++i;
    }

    // copy all the fields back, and change the length of m_Fields.
    m_LibEntry->SetFields( m_FieldsBuf );

    m_Parent->OnModify( );

    EndModal( 0 );
}


/******************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::reinitializeFieldsIdAndDefaultNames( )
/*****************************************************************************/
{
    for( unsigned new_id = FIELD1; new_id < m_FieldsBuf.size(); new_id++ )
    {
        unsigned old_id = m_FieldsBuf[new_id].m_FieldId;
        if ( old_id != new_id )
        {
            if ( m_FieldsBuf[new_id].m_Name == ReturnDefaultFieldName( old_id ) )
                 m_FieldsBuf[new_id].m_Name = ReturnDefaultFieldName( new_id );
            m_FieldsBuf[new_id].m_FieldId = new_id;
        }
    }
}


/**************************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::addFieldButtonHandler( wxCommandEvent& event )
/**************************************************************************************/
{
    // in case m_FieldsBuf[REFERENCE].m_Orient has changed on screen only, grab
    // screen contents.
    if( !copyPanelToSelectedField() )
        return;

    unsigned     fieldNdx = m_FieldsBuf.size();

    LIB_FIELD blank( fieldNdx );

    m_FieldsBuf.push_back( blank );
    m_FieldsBuf[fieldNdx].m_Name = ReturnDefaultFieldName(fieldNdx);

    setRowItem( fieldNdx, m_FieldsBuf[fieldNdx] );

    m_skipCopyFromPanel = true;
    setSelectedFieldNdx( fieldNdx );
    m_skipCopyFromPanel = false;
}


/*****************************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::deleteFieldButtonHandler( wxCommandEvent& event )
/*****************************************************************************************/
/* Delete a field.
 * Fields REFERENCE and VALUE are mandatory, and cannot be deleted.
 * If a field is empty, it is removed.
 * if not empty, the text is removed.
 */
{
    unsigned fieldNdx = getSelectedFieldNdx();

    if( fieldNdx >= m_FieldsBuf.size() )    // traps the -1 case too
        return;

    if( fieldNdx <= VALUE )
    {
        wxBell();
        return;
    }

    m_skipCopyFromPanel = true;
    if( m_FieldsBuf[fieldNdx].m_Text.IsEmpty() )
    {
        m_FieldsBuf.erase( m_FieldsBuf.begin() + fieldNdx );
        fieldListCtrl->DeleteItem( fieldNdx );

        if( fieldNdx >= m_FieldsBuf.size() )
            --fieldNdx;

        // Reinitialize fields IDs and default names:
        reinitializeFieldsIdAndDefaultNames();
    }
    else
    {
        m_FieldsBuf[fieldNdx].m_Text.Empty();
        copySelectedFieldToPanel();
    }

    updateDisplay( );

    setRowItem( fieldNdx, m_FieldsBuf[fieldNdx] );
    setSelectedFieldNdx( fieldNdx );
    m_skipCopyFromPanel = false;
}


/*************************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB:: moveUpButtonHandler( wxCommandEvent& event )
/*************************************************************************************/
{
    unsigned fieldNdx = getSelectedFieldNdx();

    if( fieldNdx >= m_FieldsBuf.size() )    // traps the -1 case too
        return;

    if( fieldNdx <= FIELD1 )
    {
        wxBell();
        return;
    }

    if( !copyPanelToSelectedField() )
        return;

    // swap the fieldNdx field with the one before it, in both the vector
    // and in the fieldListCtrl
    LIB_FIELD tmp = m_FieldsBuf[fieldNdx - 1];

    m_FieldsBuf[fieldNdx - 1] = m_FieldsBuf[fieldNdx];
    setRowItem( fieldNdx - 1, m_FieldsBuf[fieldNdx] );

    m_FieldsBuf[fieldNdx] = tmp;
    setRowItem( fieldNdx, tmp );

    // Reinitialize fields IDs and default names:
    reinitializeFieldsIdAndDefaultNames();
    updateDisplay( );

    m_skipCopyFromPanel = true;
    setSelectedFieldNdx( fieldNdx - 1 );
    m_skipCopyFromPanel = false;
}


/****************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::setSelectedFieldNdx( int aFieldNdx )
/****************************************************************************/
{
    /* deselect old selection, but I think this is done by single selection flag within fieldListCtrl
     *  fieldListCtrl->SetItemState( s_SelectedRow, 0, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
     */

    if( aFieldNdx >= (int) m_FieldsBuf.size() )
        aFieldNdx = m_FieldsBuf.size() - 1;

    if( aFieldNdx < 0 )
        aFieldNdx = 0;

    fieldListCtrl->SetItemState( aFieldNdx, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED );
    fieldListCtrl->EnsureVisible( aFieldNdx );

    s_SelectedRow = aFieldNdx;
}


int DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::getSelectedFieldNdx()
{
    return s_SelectedRow;
}


static bool SortFieldsById(const LIB_FIELD& item1, const LIB_FIELD& item2)
{
    return item1.m_FieldId < item2.m_FieldId;
}

/***********************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::InitBuffers( void )
/***********************************************************/
{
    LIB_FIELD_LIST fields;

    m_LibEntry->GetFields( fields );

    // copy all the fields to a work area
    m_FieldsBuf.reserve(NUMBER_OF_FIELDS);

    // Creates a working copy of fields
    for( size_t i = 0; i < fields.size(); i++ )
        m_FieldsBuf.push_back( fields[i] );

    // Display 12 fields (or more), and add missing fields
    LIB_FIELD blank( 2 );
    unsigned fcount = m_FieldsBuf.size();
    for( unsigned ii = 2; ii < NUMBER_OF_FIELDS; ii++ )
    {
        unsigned jj;
        for ( jj = 2; jj < fcount; jj ++ )
            if ( m_FieldsBuf[jj].m_FieldId == (int)ii )     // Field id already exists, ok.
                break;
        if ( jj < fcount ) continue;
        // Field id not found: add this field
        blank.m_FieldId = ii;
        m_FieldsBuf.push_back( blank );
    }

    m_FieldsBuf[VALUE].m_Name << wxT( "/" ) << _( "Chip Name" );

    // Sort files by field id, because they are not entered by id
    sort(m_FieldsBuf.begin(), m_FieldsBuf.end(), SortFieldsById);

    // Now, all fields with Id  0 to NUMBER_OF_FIELDS-1 exist
    // init default fields names
    for( int ii = 0; ii < NUMBER_OF_FIELDS; ii++ )
    {
        if( m_FieldsBuf[ii].m_Name.IsEmpty() || ii < FIELD1 )
            m_FieldsBuf[ii].m_Name = ReturnDefaultFieldName( ii );
    }

    for( unsigned ii = 0; ii < m_FieldsBuf.size();  ++ii )
    {
        setRowItem( ii, m_FieldsBuf[ii] );
    }

    // put focus on the list ctrl
    fieldListCtrl->SetFocus();

    // resume editing at the last row edited, last time dialog was up.
    if ( s_SelectedRow < (int) m_FieldsBuf.size() )
        s_SelectedRow = 0;
    setSelectedFieldNdx( s_SelectedRow );
}


/***********************************************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::setRowItem( int aFieldNdx, const LIB_FIELD& aField )
/***********************************************************************************************/
{
    wxASSERT( aFieldNdx >= 0 );

    // insert blanks if aFieldNdx is referencing a "yet to be defined" row
    while( aFieldNdx >= fieldListCtrl->GetItemCount() )
    {
        long ndx = fieldListCtrl->InsertItem( fieldListCtrl->GetItemCount(), wxEmptyString );

        wxASSERT( ndx >= 0 );

        fieldListCtrl->SetItem( ndx, COLUMN_TEXT, wxEmptyString );
    }

    fieldListCtrl->SetItem( aFieldNdx, COLUMN_FIELD_NAME, aField.m_Name );
    fieldListCtrl->SetItem( aFieldNdx, COLUMN_TEXT, aField.m_Text );

    // recompute the column widths here, after setting texts
    fieldListCtrl->SetColumnWidth( COLUMN_FIELD_NAME, wxLIST_AUTOSIZE );
    fieldListCtrl->SetColumnWidth( COLUMN_TEXT, wxLIST_AUTOSIZE );
}


/****************************************************************/
void DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::copySelectedFieldToPanel()
/****************************************************************/
{
    unsigned fieldNdx = getSelectedFieldNdx();

    if( fieldNdx >= m_FieldsBuf.size() )    // traps the -1 case too
        return;

    LIB_FIELD& field = m_FieldsBuf[fieldNdx];

    showCheckBox->SetValue( !(field.m_Attributs & TEXT_NO_VISIBLE) );

    rotateCheckBox->SetValue( field.m_Orient == TEXT_ORIENT_VERT );

    int style = 0;
    if( field.m_Italic )
        style = 1;
    if( field.m_Bold )
        style |= 2;
    m_StyleRadioBox->SetSelection( style );

    // Copy the text justification
    if( field.m_HJustify == GR_TEXT_HJUSTIFY_LEFT )
        m_FieldHJustifyCtrl->SetSelection(0);
    else if( field.m_HJustify == GR_TEXT_HJUSTIFY_RIGHT )
        m_FieldHJustifyCtrl->SetSelection(2);
    else
        m_FieldHJustifyCtrl->SetSelection(1);

    if( field.m_VJustify == GR_TEXT_VJUSTIFY_BOTTOM )
        m_FieldVJustifyCtrl->SetSelection(0);
    else if( field.m_VJustify == GR_TEXT_VJUSTIFY_TOP )
        m_FieldVJustifyCtrl->SetSelection(2);
    else
        m_FieldVJustifyCtrl->SetSelection(1);

    fieldNameTextCtrl->SetValue( field.m_Name );

    // if fieldNdx == REFERENCE, VALUE, FOOTPRINT, or DATASHEET, then disable field name editing
    fieldNameTextCtrl->Enable(  fieldNdx >= FIELD1 );
    fieldNameTextCtrl->SetEditable( fieldNdx >= FIELD1 );
    moveUpButton->Enable( fieldNdx >= FIELD1 );   // disable move up button for non moveable fields
    // if fieldNdx == REFERENCE, VALUE, then disable delete button
    deleteFieldButton->Enable( fieldNdx > VALUE );

    fieldValueTextCtrl->SetValue( field.m_Text );

    textSizeTextCtrl->SetValue(
        WinEDA_GraphicTextCtrl::FormatSize( EESCHEMA_INTERNAL_UNIT, g_UnitMetric, field.m_Size.x ) );

    wxPoint coord = field.m_Pos;
    wxPoint zero;

    // If the field value is empty and the position is at relative zero, we set the
    // initial position as a small offset from the ref field, and orient
    // it the same as the ref field.  That is likely to put it at least
    // close to the desired position.
    if( coord == zero && field.m_Text.IsEmpty() )
    {
        rotateCheckBox->SetValue( m_FieldsBuf[REFERENCE].m_Orient == TEXT_ORIENT_VERT );

        coord.x = m_FieldsBuf[REFERENCE].m_Pos.x + (fieldNdx - FIELD1 + 1) * 100;
        coord.y = m_FieldsBuf[REFERENCE].m_Pos.y + (fieldNdx - FIELD1 + 1) * 100;

        // coord can compute negative if field is < FIELD1, e.g. FOOTPRINT.
        // That is ok, we basically don't want all the new empty fields on
        // top of each other.
    }

    wxString coordText = ReturnStringFromValue( g_UnitMetric, coord.x, EESCHEMA_INTERNAL_UNIT );
    posXTextCtrl->SetValue( coordText );

    // Note: the Y axis for components in lib is from bottom to top
    // and the screen axis is top to bottom: we must change the y coord sign for editing
    NEGATE( coord.y );
    coordText = ReturnStringFromValue( g_UnitMetric, coord.y, EESCHEMA_INTERNAL_UNIT );
    posYTextCtrl->SetValue( coordText );
}


/*****************************************************************/
bool DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::copyPanelToSelectedField()
/*****************************************************************/
{
    unsigned fieldNdx = getSelectedFieldNdx();

    if( fieldNdx >= m_FieldsBuf.size() )        // traps the -1 case too
        return true;

    LIB_FIELD& field = m_FieldsBuf[fieldNdx];

    if( showCheckBox->GetValue() )
        field.m_Attributs &= ~TEXT_NO_VISIBLE;
    else
        field.m_Attributs |= TEXT_NO_VISIBLE;

    if( rotateCheckBox->GetValue() )
        field.m_Orient = TEXT_ORIENT_VERT;
    else
        field.m_Orient = TEXT_ORIENT_HORIZ;

    // Copy the text justification
    GRTextHorizJustifyType hjustify[3] = {
        GR_TEXT_HJUSTIFY_LEFT, GR_TEXT_HJUSTIFY_CENTER,
        GR_TEXT_HJUSTIFY_RIGHT
    };
    GRTextVertJustifyType vjustify[3] = {
        GR_TEXT_VJUSTIFY_BOTTOM, GR_TEXT_VJUSTIFY_CENTER,
        GR_TEXT_VJUSTIFY_TOP
    };
    field.m_HJustify = hjustify[m_FieldHJustifyCtrl->GetSelection()];
    field.m_VJustify = vjustify[m_FieldVJustifyCtrl->GetSelection()];

    /* Void fields texts for REFERENCE and VALUE (value is the name of the component in lib ! ) are not allowed
     * change them only for a new non void value
     */
    if( !fieldValueTextCtrl->GetValue().IsEmpty() || fieldNdx > VALUE )
        field.m_Text = fieldValueTextCtrl->GetValue();

    field.m_Name = fieldNameTextCtrl->GetValue();

    setRowItem( fieldNdx, field );  // update fieldListCtrl

    field.m_Size.x = WinEDA_GraphicTextCtrl::ParseSize(
        textSizeTextCtrl->GetValue(), EESCHEMA_INTERNAL_UNIT, g_UnitMetric );
    field.m_Size.y = field.m_Size.x;

    int style = m_StyleRadioBox->GetSelection();
    if( (style & 1 ) != 0 )
        field.m_Italic = true;
    else
        field.m_Italic = false;

    if( (style & 2 ) != 0 )
        field.m_Bold = true;
    else
	field.m_Bold = false;

    double value;

    posXTextCtrl->GetValue().ToDouble( &value );
    field.m_Pos.x = From_User_Unit( g_UnitMetric, value, EESCHEMA_INTERNAL_UNIT );

    posYTextCtrl->GetValue().ToDouble( &value );
    field.m_Pos.y = From_User_Unit( g_UnitMetric, value, EESCHEMA_INTERNAL_UNIT );

    // Note: the Y axis for components in lib is from bottom to top
    // and the screen axis is top to bottom: we must change the y coord sign for editing
    NEGATE( field.m_Pos.y );

    return true;
}
