/**************************************************************
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"

//------------------------------------------------------------------

#include "solveroptions.hxx"
#include "solveroptions.hrc"
#include "scresid.hxx"
#include "global.hxx"
#include "miscuno.hxx"
#include "solverutil.hxx"

#include <rtl/math.hxx>
#include <vcl/msgbox.hxx>
#include <unotools/collatorwrapper.hxx>
#include <unotools/localedatawrapper.hxx>

#include <algorithm>

#include <com/sun/star/sheet/Solver.hpp>
#include <com/sun/star/sheet/XSolverDescription.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>

using namespace com::sun::star;

//==================================================================

/// Helper for sorting properties
struct ScSolverOptionsEntry
{
    sal_Int32       nPosition;
    rtl::OUString   aDescription;

    ScSolverOptionsEntry() : nPosition(0) {}

    bool operator< (const ScSolverOptionsEntry& rOther) const
    {
        return ( ScGlobal::GetCollator()->compareString( aDescription, rOther.aDescription ) == COMPARE_LESS );
    }
};

//------------------------------------------------------------------

class ScSolverOptionsString : public SvLBoxString
{
    bool        mbIsDouble;
    double      mfDoubleValue;
    sal_Int32   mnIntValue;

public:
    ScSolverOptionsString( SvLBoxEntry* pEntry, sal_uInt16 nFlags, const String& rStr ) :
        SvLBoxString( pEntry, nFlags, rStr ),
        mbIsDouble( false ),
        mfDoubleValue( 0.0 ),
        mnIntValue( 0 ) {}

    bool      IsDouble() const        { return mbIsDouble; }
    double    GetDoubleValue() const  { return mfDoubleValue; }
    sal_Int32 GetIntValue() const     { return mnIntValue; }

    void      SetDoubleValue( double fNew ) { mbIsDouble = true; mfDoubleValue = fNew; }
    void      SetIntValue( sal_Int32 nNew ) { mbIsDouble = false; mnIntValue = nNew; }

    virtual void Paint( const Point& rPos, SvLBox& rDev, sal_uInt16 nFlags, SvLBoxEntry* pEntry );
};

void ScSolverOptionsString::Paint( const Point& rPos, SvLBox& rDev, sal_uInt16, SvLBoxEntry* /* pEntry */ )
{
    //! move position? (SvxLinguTabPage: aPos.X() += 20)
    String aNormalStr( GetText() );
    aNormalStr.Append( (sal_Unicode) ':' );
    rDev.DrawText( rPos, aNormalStr );

    Point aNewPos( rPos );
    aNewPos.X() += rDev.GetTextWidth( aNormalStr );
    Font aOldFont( rDev.GetFont() );
    Font aFont( aOldFont );
    aFont.SetWeight( WEIGHT_BOLD );

    String sTxt( ' ' );
    if ( mbIsDouble )
        sTxt += (String)rtl::math::doubleToUString( mfDoubleValue,
            rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
            ScGlobal::GetpLocaleData()->getNumDecimalSep().GetChar(0), true );
    else
        sTxt += String::CreateFromInt32( mnIntValue );
    rDev.SetFont( aFont );
    rDev.DrawText( aNewPos, sTxt );

    rDev.SetFont( aOldFont );
}

//------------------------------------------------------------------

ScSolverOptionsDialog::ScSolverOptionsDialog( Window* pParent,
                        const uno::Sequence<rtl::OUString>& rImplNames,
                        const uno::Sequence<rtl::OUString>& rDescriptions,
                        const String& rEngine,
                        const uno::Sequence<beans::PropertyValue>& rProperties )
    : ModalDialog( pParent, ScResId( RID_SCDLG_SOLVEROPTIONS ) ),
    maFtEngine      ( this, ScResId( FT_ENGINE ) ),
    maLbEngine      ( this, ScResId( LB_ENGINE ) ),
    maFtSettings    ( this, ScResId( FT_SETTINGS ) ),
    maLbSettings    ( this, ScResId( LB_SETTINGS ) ),
    maBtnEdit       ( this, ScResId( BTN_EDIT ) ),
    maFlButtons     ( this, ScResId( FL_BUTTONS ) ),
    maBtnHelp       ( this, ScResId( BTN_HELP ) ),
    maBtnOk         ( this, ScResId( BTN_OK ) ),
    maBtnCancel     ( this, ScResId( BTN_CANCEL ) ),
    mpCheckButtonData( NULL ),
    maImplNames( rImplNames ),
    maDescriptions( rDescriptions ),
    maEngine( rEngine ),
    maProperties( rProperties )
{
    maLbEngine.SetSelectHdl( LINK( this, ScSolverOptionsDialog, EngineSelectHdl ) );

    maBtnEdit.SetClickHdl( LINK( this, ScSolverOptionsDialog, ButtonHdl ) );

    maLbSettings.SetStyle( maLbSettings.GetStyle()|WB_CLIPCHILDREN|WB_FORCE_MAKEVISIBLE );
    maLbSettings.SetHelpId( HID_SC_SOLVEROPTIONS_LB );
    maLbSettings.SetHighlightRange();

    maLbSettings.SetSelectHdl( LINK( this, ScSolverOptionsDialog, SettingsSelHdl ) );
    maLbSettings.SetDoubleClickHdl( LINK( this, ScSolverOptionsDialog, SettingsDoubleClickHdl ) );

    sal_Int32 nSelect = -1;
    sal_Int32 nImplCount = maImplNames.getLength();
    for (sal_Int32 nImpl=0; nImpl<nImplCount; ++nImpl)
    {
        String aImplName( maImplNames[nImpl] );
        String aDescription( maDescriptions[nImpl] );   // user-visible descriptions in list box
        maLbEngine.InsertEntry( aDescription );
        if ( aImplName == maEngine )
            nSelect = nImpl;
    }
    if ( nSelect < 0 )                  // no (valid) engine given
    {
        if ( nImplCount > 0 )
        {
            maEngine = maImplNames[0];  // use first implementation
            nSelect = 0;
        }
        else
            maEngine.Erase();
        maProperties.realloc(0);        // don't use options from different engine
    }
    if ( nSelect >= 0 )                 // select in list box
        maLbEngine.SelectEntryPos( static_cast<sal_uInt16>(nSelect) );

    if ( !maProperties.getLength() )
        ReadFromComponent();            // fill maProperties from component (using maEngine)
    FillListBox();                      // using maProperties

    FreeResource();
}

ScSolverOptionsDialog::~ScSolverOptionsDialog()
{
    delete mpCheckButtonData;
}

const String& ScSolverOptionsDialog::GetEngine() const
{
    return maEngine;    // already updated in selection handler
}

const uno::Sequence<beans::PropertyValue>& ScSolverOptionsDialog::GetProperties()
{
    // update maProperties from list box content
    // order of entries in list box and maProperties is the same
    sal_Int32 nEntryCount = maProperties.getLength();
    SvLBoxTreeList* pModel = maLbSettings.GetModel();
    if ( nEntryCount == (sal_Int32)pModel->GetEntryCount() )
    {
        for (sal_Int32 nEntryPos=0; nEntryPos<nEntryCount; ++nEntryPos)
        {
            uno::Any& rValue = maProperties[nEntryPos].Value;
            SvLBoxEntry* pEntry = pModel->GetEntry(nEntryPos);

            bool bHasData = false;
            sal_uInt16 nItemCount = pEntry->ItemCount();
            for (sal_uInt16 nItemPos=0; nItemPos<nItemCount && !bHasData; ++nItemPos)
            {
                SvLBoxItem* pItem = pEntry->GetItem( nItemPos );
                ScSolverOptionsString* pStringItem = dynamic_cast<ScSolverOptionsString*>(pItem);
                if ( pStringItem )
                {
                    if ( pStringItem->IsDouble() )
                        rValue <<= pStringItem->GetDoubleValue();
                    else
                        rValue <<= pStringItem->GetIntValue();
                    bHasData = true;
                }
            }
            if ( !bHasData )
                ScUnoHelpFunctions::SetBoolInAny( rValue,
                                    maLbSettings.GetCheckButtonState( pEntry ) == SV_BUTTON_CHECKED );
        }
    }
    else
    {
        DBG_ERRORFILE( "wrong count" );
    }

    return maProperties;
}

void ScSolverOptionsDialog::FillListBox()
{
    // get property descriptions, sort by them

    uno::Reference<sheet::XSolverDescription> xDesc( ScSolverUtil::GetSolver( maEngine ), uno::UNO_QUERY );
    sal_Int32 nCount = maProperties.getLength();
    std::vector<ScSolverOptionsEntry> aDescriptions( nCount );
    for (sal_Int32 nPos=0; nPos<nCount; nPos++)
    {
        rtl::OUString aPropName( maProperties[nPos].Name );
        rtl::OUString aVisName;
        if ( xDesc.is() )
            aVisName = xDesc->getPropertyDescription( aPropName );
        if ( !aVisName.getLength() )
            aVisName = aPropName;
        aDescriptions[nPos].nPosition = nPos;
        aDescriptions[nPos].aDescription = aVisName;
    }
    std::sort( aDescriptions.begin(), aDescriptions.end() );

    // also update maProperties to the order of descriptions

    uno::Sequence<beans::PropertyValue> aNewSeq;
    aNewSeq.realloc( nCount );
    for (sal_Int32 nPos=0; nPos<nCount; nPos++)
        aNewSeq[nPos] = maProperties[ aDescriptions[nPos].nPosition ];
    maProperties = aNewSeq;

    // fill the list box

    maLbSettings.SetUpdateMode(sal_False);
    maLbSettings.Clear();

    String sEmpty;
    if (!mpCheckButtonData)
        mpCheckButtonData = new SvLBoxButtonData( &maLbSettings );

    SvLBoxTreeList* pModel = maLbSettings.GetModel();
    SvLBoxEntry* pEntry = NULL;

    for (sal_Int32 nPos=0; nPos<nCount; nPos++)
    {
        rtl::OUString aVisName = aDescriptions[nPos].aDescription;

        uno::Any aValue = maProperties[nPos].Value;
        uno::TypeClass eClass = aValue.getValueTypeClass();
        if ( eClass == uno::TypeClass_BOOLEAN )
        {
            // check box entry
            pEntry = new SvLBoxEntry;
            SvLBoxButton* pButton = new SvLBoxButton( pEntry, SvLBoxButtonKind_enabledCheckbox, 0, mpCheckButtonData );
            if ( ScUnoHelpFunctions::GetBoolFromAny( aValue ) )
                pButton->SetStateChecked();
            else
                pButton->SetStateUnchecked();
            pEntry->AddItem( pButton );
            pEntry->AddItem( new SvLBoxContextBmp( pEntry, 0, Image(), Image(), 0 ) );
            pEntry->AddItem( new SvLBoxString( pEntry, 0, aVisName ) );
        }
        else
        {
            // value entry
            pEntry = new SvLBoxEntry;
            pEntry->AddItem( new SvLBoxString( pEntry, 0, sEmpty ) );                   // empty column
            pEntry->AddItem( new SvLBoxContextBmp( pEntry, 0, Image(), Image(), 0 ) );
            ScSolverOptionsString* pItem = new ScSolverOptionsString( pEntry, 0, aVisName );
            if ( eClass == uno::TypeClass_DOUBLE )
            {
                double fDoubleValue = 0.0;
                if ( aValue >>= fDoubleValue )
                    pItem->SetDoubleValue( fDoubleValue );
            }
            else
            {
                sal_Int32 nIntValue = 0;
                if ( aValue >>= nIntValue )
                    pItem->SetIntValue( nIntValue );
            }
            pEntry->AddItem( pItem );
        }
        pModel->Insert( pEntry );
    }

    maLbSettings.SetUpdateMode(sal_True);
}

void ScSolverOptionsDialog::ReadFromComponent()
{
    maProperties = ScSolverUtil::GetDefaults( maEngine );
}

void ScSolverOptionsDialog::EditOption()
{
    SvLBoxEntry* pEntry = maLbSettings.GetCurEntry();
    if (pEntry)
    {
        sal_uInt16 nItemCount = pEntry->ItemCount();
        for (sal_uInt16 nPos=0; nPos<nItemCount; ++nPos)
        {
            SvLBoxItem* pItem = pEntry->GetItem( nPos );
            ScSolverOptionsString* pStringItem = dynamic_cast<ScSolverOptionsString*>(pItem);
            if ( pStringItem )
            {
                if ( pStringItem->IsDouble() )
                {
                    ScSolverValueDialog aValDialog( this );
                    aValDialog.SetOptionName( pStringItem->GetText() );
                    aValDialog.SetValue( pStringItem->GetDoubleValue() );
                    if ( aValDialog.Execute() == RET_OK )
                    {
                        pStringItem->SetDoubleValue( aValDialog.GetValue() );
                        maLbSettings.InvalidateEntry( pEntry );
                    }
                }
                else
                {
                    ScSolverIntegerDialog aIntDialog( this );
                    aIntDialog.SetOptionName( pStringItem->GetText() );
                    aIntDialog.SetValue( pStringItem->GetIntValue() );
                    if ( aIntDialog.Execute() == RET_OK )
                    {
                        pStringItem->SetIntValue( aIntDialog.GetValue() );
                        maLbSettings.InvalidateEntry( pEntry );
                    }
                }
            }
        }
    }
}

IMPL_LINK( ScSolverOptionsDialog, ButtonHdl, PushButton*, pBtn )
{
    if ( pBtn == &maBtnEdit )
        EditOption();

    return 0;
}

IMPL_LINK( ScSolverOptionsDialog, SettingsDoubleClickHdl, SvTreeListBox*, EMPTYARG )
{
    EditOption();
    return 0;
}

IMPL_LINK( ScSolverOptionsDialog, EngineSelectHdl, ListBox*, EMPTYARG )
{
    sal_uInt16 nSelectPos = maLbEngine.GetSelectEntryPos();
    if ( nSelectPos < maImplNames.getLength() )
    {
        String aNewEngine( maImplNames[nSelectPos] );
        if ( aNewEngine != maEngine )
        {
            maEngine = aNewEngine;
            ReadFromComponent();            // fill maProperties from component (using maEngine)
            FillListBox();                  // using maProperties
        }
    }
    return 0;
}

IMPL_LINK( ScSolverOptionsDialog, SettingsSelHdl, SvxCheckListBox*, EMPTYARG )
{
    sal_Bool bCheckbox = sal_False;

    SvLBoxEntry* pEntry = maLbSettings.GetCurEntry();
    if (pEntry)
    {
        SvLBoxItem* pItem = pEntry->GetFirstItem(SV_ITEM_ID_LBOXBUTTON);
        if ( pItem && pItem->IsA() == SV_ITEM_ID_LBOXBUTTON )
            bCheckbox = sal_True;
    }

    maBtnEdit.Enable( !bCheckbox );

    return 0;
}

//------------------------------------------------------------------

ScSolverIntegerDialog::ScSolverIntegerDialog( Window * pParent )
    : ModalDialog( pParent, ScResId( RID_SCDLG_SOLVER_INTEGER ) ),
    maFtName        ( this, ScResId( FT_OPTIONNAME ) ),
    maNfValue       ( this, ScResId( NF_VALUE ) ),
    maFlButtons     ( this, ScResId( FL_BUTTONS ) ),
    maBtnOk         ( this, ScResId( BTN_OK ) ),
    maBtnCancel     ( this, ScResId( BTN_CANCEL ) )
{
    FreeResource();
}

ScSolverIntegerDialog::~ScSolverIntegerDialog()
{
}

void ScSolverIntegerDialog::SetOptionName( const String& rName )
{
    maFtName.SetText( rName );
}

void ScSolverIntegerDialog::SetValue( sal_Int32 nValue )
{
    maNfValue.SetValue( nValue );
}

sal_Int32 ScSolverIntegerDialog::GetValue() const
{
    sal_Int64 nValue = maNfValue.GetValue();
    if ( nValue < SAL_MIN_INT32 )
        return SAL_MIN_INT32;
    if ( nValue > SAL_MAX_INT32 )
        return SAL_MAX_INT32;
    return (sal_Int32) nValue;
}

//------------------------------------------------------------------

ScSolverValueDialog::ScSolverValueDialog( Window * pParent )
    : ModalDialog( pParent, ScResId( RID_SCDLG_SOLVER_DOUBLE ) ),
    maFtName        ( this, ScResId( FT_OPTIONNAME ) ),
    maEdValue       ( this, ScResId( ED_VALUE ) ),
    maFlButtons     ( this, ScResId( FL_BUTTONS ) ),
    maBtnOk         ( this, ScResId( BTN_OK ) ),
    maBtnCancel     ( this, ScResId( BTN_CANCEL ) )
{
    FreeResource();
}

ScSolverValueDialog::~ScSolverValueDialog()
{
}

void ScSolverValueDialog::SetOptionName( const String& rName )
{
    maFtName.SetText( rName );
}

void ScSolverValueDialog::SetValue( double fValue )
{
    maEdValue.SetText( rtl::math::doubleToUString( fValue,
            rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
            ScGlobal::GetpLocaleData()->getNumDecimalSep().GetChar(0), true ) );
}

double ScSolverValueDialog::GetValue() const
{
    String aInput = maEdValue.GetText();

    const LocaleDataWrapper* pLocaleData = ScGlobal::GetpLocaleData();
    rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
    double fValue = rtl::math::stringToDouble( aInput,
                            pLocaleData->getNumDecimalSep().GetChar(0),
                            pLocaleData->getNumThousandSep().GetChar(0),
                            &eStatus, NULL );
    return fValue;
}