/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */


#include <wx/msgdlg.h>
#include <pgm_base.h>
#include <html_messagebox.h>
#include "3d_cache/dialogs/dlg_3d_pathconfig.h"
#include "3d_cache/3d_filename_resolver.h"

DLG_3D_PATH_CONFIG::DLG_3D_PATH_CONFIG( wxWindow* aParent, S3D_FILENAME_RESOLVER* aResolver ) :
    DLG_3D_PATH_CONFIG_BASE( aParent ), m_resolver( aResolver )
{
    initDialog();

    GetSizer()->SetSizeHints( this );
	Centre();

    return;
}


void DLG_3D_PATH_CONFIG::initDialog()
{
    m_Aliases->EnableEditing( true );

    // Gives a min width to each column, when the user drags a column
    m_Aliases->SetColMinimalWidth( 0, 80 );
    m_Aliases->SetColMinimalWidth( 1, 300 );
    m_Aliases->SetColMinimalWidth( 2, 120 );
    m_Aliases->SetColMinimalAcceptableWidth( 80 );

    // Set column sizes to this min value
	m_Aliases->SetColSize( 0, 80 );
	m_Aliases->SetColSize( 1, 300 );
	m_Aliases->SetColSize( 2, 120 );

    m_EnvVars->SetColMinimalWidth( 0, 80 );
    m_EnvVars->SetColMinimalWidth( 1, 300 );
    m_EnvVars->SetColMinimalAcceptableWidth( 80 );
    m_EnvVars->SetColSize( 0, 80 );
    m_EnvVars->SetColSize( 1, 300 );

    if( m_resolver )
    {
        updateEnvVars();

        // prohibit these characters in the alias names: []{}()%~<>"='`;:.,&?/\|$
        m_aliasValidator.SetStyle( wxFILTER_EXCLUDE_CHAR_LIST );
        m_aliasValidator.SetCharExcludes( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) );

        const std::list< S3D_ALIAS >* rpaths = m_resolver->GetPaths();
        std::list< S3D_ALIAS >::const_iterator rI = rpaths->begin();
        std::list< S3D_ALIAS >::const_iterator rE = rpaths->end();
        size_t listsize = rpaths->size();
        size_t listidx = 0;

        while( rI != rE && ( (*rI).m_alias.StartsWith( "${" ) || (*rI).m_alias.StartsWith( "$(" ) ) )
        {
            ++listidx;
            ++rI;
        }

        if( listidx < listsize )
            m_curdir = (*rI).m_pathexp;
        else
            return;

        listsize = listsize - listidx - m_Aliases->GetNumberRows();

        // note: if the list allocation fails we have bigger problems
        // and there is no point in trying to notify the user here
        if( listsize > 0 && !m_Aliases->InsertRows( 0, listsize ) )
            return;

        int nitems = 0;
        wxGridCellTextEditor* pEdAlias;

        while( rI != rE )
        {
            m_Aliases->SetCellValue( nitems, 0, rI->m_alias );

            if( 0 == nitems )
            {
                m_Aliases->SetCellEditor( nitems, 0, new wxGridCellTextEditor );
                pEdAlias = (wxGridCellTextEditor*) m_Aliases->GetCellEditor( nitems, 0 );
                pEdAlias->SetValidator( m_aliasValidator );
                pEdAlias->DecRef();
            }
            else
            {
                pEdAlias->IncRef();
                m_Aliases->SetCellEditor( nitems, 0, pEdAlias );
            }

            m_Aliases->SetCellValue( nitems, 1, rI->m_pathvar );
            m_Aliases->SetCellValue( nitems++, 2, rI->m_description );

            // TODO: implement a wxGridCellEditor which invokes a wxDirDialog

            ++rI;
        }

        m_Aliases->AutoSize();
    }
}


bool DLG_3D_PATH_CONFIG::TransferDataFromWindow()
{
    if( NULL == m_resolver )
    {
        wxMessageBox( _( "[BUG] No valid resolver; data will not be updated" ),
            _( "Update 3D search path list" ) );

        return false;
    }

    std::vector<S3D_ALIAS> alist;
    S3D_ALIAS alias;

    int ni = m_Aliases->GetNumberRows();

    if( ni <= 0 )
    {
        // note: UI usability: we should ask a user if they're sure they
        // want to clear the entire path list
        m_resolver->UpdatePathList( alist );
        return true;
    }

    for( int i = 0; i < ni; ++i )
    {
        alias.m_alias = m_Aliases->GetCellValue( i, 0 );
        alias.m_pathvar = m_Aliases->GetCellValue( i, 1 );
        alias.m_description = m_Aliases->GetCellValue( i, 2 );

        if( !alias.m_alias.empty() && !alias.m_pathvar.empty() )
            alist.push_back( alias );

    }

    return m_resolver->UpdatePathList( alist );
}


void DLG_3D_PATH_CONFIG::OnAddAlias( wxCommandEvent& event )
{
    int ni = m_Aliases->GetNumberRows();

    if( m_Aliases->InsertRows( ni, 1 ) )
    {
        wxGridCellTextEditor* pEdAlias;
        pEdAlias = (wxGridCellTextEditor*) m_Aliases->GetCellEditor( 0, 0 );
        m_Aliases->SetCellEditor( ni, 0, pEdAlias );
        m_Aliases->SelectRow( ni, false );
        m_Aliases->AutoSize();
        Fit();

        // TODO: set the editors on any newly created rows
    }

    event.Skip();
}


void DLG_3D_PATH_CONFIG::OnDelAlias( wxCommandEvent& event )
{
    wxArrayInt sel = m_Aliases->GetSelectedRows();

    if( sel.empty() )
    {
        wxMessageBox( _( "No entry selected" ), _( "Delete alias entry" ) );
        return;
    }

    if( sel.size() > 1 )
    {
        wxMessageBox( _( "Multiple entries selected; please\nselect only one entry" ),
            _( "Delete alias entry" ) );
        return;
    }

    if( m_Aliases->GetNumberRows() > 1 )
    {
        int ni = sel.front();
        m_Aliases->DeleteRows( ni, 1 );

        if( ni >= m_Aliases->GetNumberRows() )
            ni = m_Aliases->GetNumberRows() - 1;

        m_Aliases->SelectRow( ni, false );
        m_Aliases->AutoSize();
        Fit();
    }
    else
    {
        m_Aliases->SetCellValue( 0, 0, wxEmptyString );
        m_Aliases->SetCellValue( 0, 1, wxEmptyString );
        m_Aliases->SetCellValue( 0, 2, wxEmptyString );
    }

    event.Skip();
}


void DLG_3D_PATH_CONFIG::OnAliasMoveUp( wxCommandEvent& event )
{
    wxArrayInt sel = m_Aliases->GetSelectedRows();

    if( sel.empty() )
    {
        wxMessageBox( _( "No entry selected" ), _( "Move alias up" ) );
        return;
    }

    if( sel.size() > 1 )
    {
        wxMessageBox( _( "Multiple entries selected; please\nselect only one entry" ),
                      _( "Move alias up" ) );
        return;
    }

    int ci = sel.front();

    if( ci > 0 )
    {
        S3D_ALIAS al0;
        al0.m_alias = m_Aliases->GetCellValue( ci, 0 );
        al0.m_pathvar = m_Aliases->GetCellValue( ci, 1 );
        al0.m_description = m_Aliases->GetCellValue( ci, 2 );

        int ni = ci - 1;
        m_Aliases->SetCellValue( ci, 0, m_Aliases->GetCellValue( ni, 0 ) );
        m_Aliases->SetCellValue( ci, 1, m_Aliases->GetCellValue( ni, 1 ) );
        m_Aliases->SetCellValue( ci, 2, m_Aliases->GetCellValue( ni, 2 ) );

        m_Aliases->SetCellValue( ni, 0, al0.m_alias );
        m_Aliases->SetCellValue( ni, 1, al0.m_pathvar );
        m_Aliases->SetCellValue( ni, 2, al0.m_description );
        m_Aliases->SelectRow( ni, false );
    }

    event.Skip();
}


void DLG_3D_PATH_CONFIG::OnAliasMoveDown( wxCommandEvent& event )
{
    wxArrayInt sel = m_Aliases->GetSelectedRows();

    if( sel.empty() )
    {
        wxMessageBox( _( "No entry selected" ), _( "Move alias down" ) );
        return;
    }

    if( sel.size() > 1 )
    {
        wxMessageBox( _( "Multiple entries selected; please\nselect only one entry" ),
                      _( "Move alias down" ) );
        return;
    }

    int ni = m_Aliases->GetNumberRows() - 1;
    int ci = sel.front();

    if( ci < ni )
    {
        S3D_ALIAS al0;
        al0.m_alias = m_Aliases->GetCellValue( ci, 0 );
        al0.m_pathvar = m_Aliases->GetCellValue( ci, 1 );
        al0.m_description = m_Aliases->GetCellValue( ci, 2 );

        ni = ci + 1;
        m_Aliases->SetCellValue( ci, 0, m_Aliases->GetCellValue( ni, 0 ) );
        m_Aliases->SetCellValue( ci, 1, m_Aliases->GetCellValue( ni, 1 ) );
        m_Aliases->SetCellValue( ci, 2, m_Aliases->GetCellValue( ni, 2 ) );

        m_Aliases->SetCellValue( ni, 0, al0.m_alias );
        m_Aliases->SetCellValue( ni, 1, al0.m_pathvar );
        m_Aliases->SetCellValue( ni, 2, al0.m_description );
        m_Aliases->SelectRow( ni, false );
    }

    event.Skip();
}


void DLG_3D_PATH_CONFIG::OnConfigEnvVar( wxCommandEvent& event )
{
    Pgm().ConfigurePaths( this );
    updateEnvVars();
    event.Skip();
}


void DLG_3D_PATH_CONFIG::updateEnvVars( void )
{
    if( !m_resolver )
        return;

    std::list< wxString > epaths;

    m_resolver->GetKicadPaths( epaths );
    size_t nitems = epaths.size();
    size_t nrows = m_EnvVars->GetNumberRows();
    bool resize = nrows != nitems;  // true after adding/removing env vars

    if( nrows > nitems )
    {
        size_t ni = nrows - nitems;
        m_EnvVars->DeleteRows( 0, ni );
    }
    else if( nrows < nitems )
    {
        size_t ni = nitems - nrows;
        m_EnvVars->InsertRows( 0, ni );
    }

    int j = 0;

    for( const auto& i : epaths )
    {
        wxString val = ExpandEnvVarSubstitutions( i );
        m_EnvVars->SetCellValue( j, 0, i );
        m_EnvVars->SetCellValue( j, 1, val );
        m_EnvVars->SetReadOnly( j, 0, true );
        m_EnvVars->SetReadOnly( j, 1, true );
        wxGridCellAttr* ap = m_EnvVars->GetOrCreateCellAttr( j, 0 );
        ap->SetReadOnly( true );
        ap->SetBackgroundColour( *wxLIGHT_GREY );
        m_EnvVars->SetRowAttr( j, ap );
        ++j;
    }

    m_EnvVars->AutoSize();

    // Resizing the full dialog is sometimes needed for a clean display
    // i.e. when adding/removing Kicad environment variables
    if( resize )
        GetSizer()->SetSizeHints( this );

    return;
}


void DLG_3D_PATH_CONFIG::OnHelp( wxCommandEvent& event )
{
    wxString msg = _( "Enter the name and path for each 3D alias variable.<br>KiCad "
                      "environment variables and their values are shown for "
                      "reference only and cannot be edited." );
    msg << "<br><br><b>";
    msg << _( "Alias names may not contain any of the characters " );
    msg << "{}[]()%~<>\"='`;:.,&?/\\|$";
    msg << "</b>";

    HTML_MESSAGE_BOX dlg( GetParent(), _( "Environment Variable Help" ) );
    dlg.AddHTML_Text( msg );
    dlg.ShowModal();

    event.Skip();
}
