/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include <csvtablebox.hxx>
#include <vcl/settings.hxx>

ScCsvTableBox::ScCsvTableBox(weld::Builder& rBuilder)
    : mxRuler(new ScCsvRuler(maData, this))
    , mxGrid(new ScCsvGrid(maData, rBuilder.weld_menu(u"popup"_ustr), this))
    , mxScroll(rBuilder.weld_scrolled_window(u"scrolledwindow"_ustr, true))
    , mxRulerWeld(new weld::CustomWeld(rBuilder, u"csvruler"_ustr, *mxRuler))
    , mxGridWeld(new weld::CustomWeld(rBuilder, u"csvgrid"_ustr, *mxGrid))
    , maEndScrollIdle("ScCsvTableBox maEndScrollIdle")
{
    const OutputDevice& rRefDev = mxGrid->GetDrawingArea()->get_ref_device();
    Size aSize(rRefDev.approximate_digit_width() * 67,
               rRefDev.GetTextHeight() * 10);
    // this needs to be larger than the ScCsvGrid initial size to get it
    // to stretch to fit, see ScCsvGrid::SetDrawingArea
    mxScroll->set_size_request(aSize.Width(), aSize.Height());

    mbFixedMode = false;
    mnFixedWidth = 1;

    Link<ScCsvControl&,void> aLink = LINK( this, ScCsvTableBox, CsvCmdHdl );
    mxRuler->SetCmdHdl( aLink );
    mxGrid->SetCmdHdl( aLink );

    mxScroll->connect_hadjustment_value_changed(LINK(this, ScCsvTableBox, HScrollHdl));
    mxScroll->connect_vadjustment_value_changed(LINK(this, ScCsvTableBox, VScrollHdl));

    maEndScrollIdle.SetPriority(TaskPriority::LOWEST);
    maEndScrollIdle.SetInvokeHandler(LINK(this,ScCsvTableBox,ScrollEndHdl));

    InitControls();
}

ScCsvTableBox::~ScCsvTableBox()
{
}

// common table box handling --------------------------------------------------

void ScCsvTableBox::Refresh()
{
    mxGrid->DisableRepaint();
    mxGrid->Execute( CSVCMD_SETLINEOFFSET, 0 );
    if (mbFixedMode)
    {
        mxGrid->Execute( CSVCMD_SETPOSCOUNT, mnFixedWidth );
        mxGrid->SetSplits( mxRuler->GetSplits() );
        mxGrid->SetColumnStates( std::vector(maFixColStates) );
    }
    else
    {
        mxGrid->Execute( CSVCMD_SETPOSCOUNT, 1 );
        mxGrid->Execute( CSVCMD_NEWCELLTEXTS );
        mxGrid->SetColumnStates( std::vector(maSepColStates) );
    }
    InitControls();
    mxGrid->EnableRepaint();
}

void ScCsvTableBox::SetSeparatorsMode()
{
    if( !mbFixedMode )
        return;

    // rescue data for fixed width mode
    mnFixedWidth = mxGrid->GetPosCount();
    maFixColStates = mxGrid->GetColumnStates();
    // switch to separators mode
    mbFixedMode = false;
    // reset and reinitialize controls
    Refresh();
}

void ScCsvTableBox::SetFixedWidthMode()
{
    if( mbFixedMode )
        return;

    // rescue data for separators mode
    maSepColStates = mxGrid->GetColumnStates();
    // switch to fixed width mode
    mbFixedMode = true;
    // reset and reinitialize controls
    Refresh();
}

void ScCsvTableBox::Init()
{
    mxGrid->Init();
}

void ScCsvTableBox::InitControls()
{
    mxGrid->UpdateLayoutData();

    mxGrid->Show();
    if (mbFixedMode)
        mxRuler->Show();
    else
        mxRuler->Hide();

    Size aWinSize = mxGrid->GetOutputSizePixel();
    maData.mnWinWidth = aWinSize.Width();
    maData.mnWinHeight = aWinSize.Height();

    // scrollbars always visible
    InitHScrollBar();

    // scrollbars always visible
    InitVScrollBar();

    // let the controls self-adjust to visible area
    mxGrid->Execute( CSVCMD_SETPOSOFFSET, mxGrid->GetFirstVisPos() );
    mxGrid->Execute( CSVCMD_SETLINEOFFSET, mxGrid->GetFirstVisLine() );
}

void ScCsvTableBox::InitHScrollBar()
{
    int nValue = mxGrid->GetFirstVisPos();
    int nUpper = mxGrid->GetPosCount() + 2;
    int nPageSize = mxGrid->GetVisPosCount();

    // Undo scrollbar RTL
    if (AllSettings::GetLayoutRTL())
        nValue = nUpper - (nValue + nPageSize);

    mxScroll->hadjustment_configure(nValue, nUpper, 1, mxGrid->GetVisPosCount() * 3 / 4, nPageSize);
}

void ScCsvTableBox::InitVScrollBar()
{
    mxScroll->vadjustment_configure(mxGrid->GetFirstVisLine(), mxGrid->GetLineCount() + 1, 1,
                                    mxGrid->GetVisLineCount() - 2, mxGrid->GetVisLineCount());
}

void ScCsvTableBox::MakePosVisible( sal_Int32 nPos )
{
    if( (0 <= nPos) && (nPos < mxGrid->GetPosCount()) )
    {
        if( nPos - CSV_SCROLL_DIST + 1 <= mxGrid->GetFirstVisPos() )
            mxGrid->Execute( CSVCMD_SETPOSOFFSET, nPos - CSV_SCROLL_DIST );
        else if( nPos + CSV_SCROLL_DIST >= mxGrid->GetLastVisPos() )
            mxGrid->Execute( CSVCMD_SETPOSOFFSET, nPos - mxGrid->GetVisPosCount() + CSV_SCROLL_DIST );
    }
}

// cell contents --------------------------------------------------------------

void ScCsvTableBox::SetUniStrings(
        const OUString* pTextLines, const OUString& rSepChars,
        sal_Unicode cTextSep, bool bMergeSep, bool bRemoveSpace )
{
    // assuming that pTextLines is a string array with size CSV_PREVIEW_LINES
    // -> will be dynamic sometime
    mxGrid->DisableRepaint();
    sal_Int32 nEndLine = mxGrid->GetFirstVisLine() + CSV_PREVIEW_LINES;
    const OUString* pString = pTextLines;
    for( sal_Int32 nLine = mxGrid->GetFirstVisLine(); nLine < nEndLine; ++nLine, ++pString )
    {
        if( mbFixedMode )
            mxGrid->ImplSetTextLineFix( nLine, *pString );
        else
            mxGrid->ImplSetTextLineSep( nLine, *pString, rSepChars, cTextSep, bMergeSep, bRemoveSpace );
    }
    mxGrid->EnableRepaint();
}

// column settings ------------------------------------------------------------

void ScCsvTableBox::InitTypes(const weld::ComboBox& rListBox)
{
    const sal_Int32 nTypeCount = rListBox.get_count();
    std::vector<OUString> aTypeNames( nTypeCount );
    for( sal_Int32 nIndex = 0; nIndex < nTypeCount; ++nIndex )
        aTypeNames[ nIndex ] = rListBox.get_text( nIndex );
    mxGrid->SetTypeNames( std::move(aTypeNames) );
}

void ScCsvTableBox::FillColumnData( ScAsciiOptions& rOptions ) const
{
    if( mbFixedMode )
        mxGrid->FillColumnDataFix( rOptions );
    else
        mxGrid->FillColumnDataSep( rOptions );
}

// event handling -------------------------------------------------------------

IMPL_LINK( ScCsvTableBox, CsvCmdHdl, ScCsvControl&, rCtrl, void )
{
    const ScCsvCmd& rCmd = rCtrl.GetCmd();
    ScCsvCmdType eType = rCmd.GetType();
    sal_Int32 nParam1 = rCmd.GetParam1();
    sal_Int32 nParam2 = rCmd.GetParam2();

    bool bFound = true;
    switch( eType )
    {
        case CSVCMD_REPAINT:
            if( !mxGrid->IsNoRepaint() )
            {
                mxGrid->Invalidate();
                mxRuler->Invalidate();
                InitHScrollBar();
                InitVScrollBar();
            }
        break;
        case CSVCMD_MAKEPOSVISIBLE:
            MakePosVisible( nParam1 );
        break;

        case CSVCMD_NEWCELLTEXTS:
            if( mbFixedMode )
                mxGrid->Execute( CSVCMD_UPDATECELLTEXTS );
            else
            {
                mxGrid->DisableRepaint();
                ScCsvColStateVec aStates( mxGrid->GetColumnStates() );
                sal_Int32 nPos = mxGrid->GetFirstVisPos();
                mxGrid->Execute( CSVCMD_SETPOSCOUNT, 1 );
                mxGrid->Execute( CSVCMD_UPDATECELLTEXTS );
                mxGrid->Execute( CSVCMD_SETPOSOFFSET, nPos );
                mxGrid->SetColumnStates( std::move(aStates) );
                mxGrid->EnableRepaint();
            }
        break;
        case CSVCMD_UPDATECELLTEXTS:
            maUpdateTextHdl.Call( *this );
        break;
        case CSVCMD_SETCOLUMNTYPE:
            mxGrid->SetSelColumnType( nParam1 );
        break;
        case CSVCMD_EXPORTCOLUMNTYPE:
            maColTypeHdl.Call( *this );
        break;
        case CSVCMD_SETFIRSTIMPORTLINE:
            mxGrid->SetFirstImportedLine( nParam1 );
        break;

        case CSVCMD_INSERTSPLIT:
            OSL_ENSURE( mbFixedMode, "ScCsvTableBox::CsvCmdHdl::InsertSplit - invalid call" );
            if( mxRuler->GetSplitCount() + 1 < sal::static_int_cast<sal_uInt32>(CSV_MAXCOLCOUNT) )
            {
                mxRuler->InsertSplit( nParam1 );
                mxGrid->InsertSplit( nParam1 );
            }
        break;
        case CSVCMD_REMOVESPLIT:
            OSL_ENSURE( mbFixedMode, "ScCsvTableBox::CsvCmdHdl::RemoveSplit - invalid call" );
            mxRuler->RemoveSplit( nParam1 );
            mxGrid->RemoveSplit( nParam1 );
        break;
        case CSVCMD_TOGGLESPLIT:
            mxGrid->Execute( mxRuler->HasSplit( nParam1 ) ? CSVCMD_REMOVESPLIT : CSVCMD_INSERTSPLIT, nParam1 );
        break;
        case CSVCMD_MOVESPLIT:
            OSL_ENSURE( mbFixedMode, "ScCsvTableBox::CsvCmdHdl::MoveSplit - invalid call" );
            mxRuler->MoveSplit( nParam1, nParam2 );
            mxGrid->MoveSplit( nParam1, nParam2 );
        break;
        case CSVCMD_REMOVEALLSPLITS:
            OSL_ENSURE( mbFixedMode, "ScCsvTableBox::CsvCmdHdl::RemoveAllSplits - invalid call" );
            mxRuler->RemoveAllSplits();
            mxGrid->RemoveAllSplits();
        break;
        default:
            bFound = false;
    }
    if( bFound )
        return;

    const ScCsvLayoutData aOldData( maData );
    switch( eType )
    {
        case CSVCMD_SETPOSCOUNT:
            maData.mnPosCount = std::max( nParam1, sal_Int32( 1 ) );
            ImplSetPosOffset( mxGrid->GetFirstVisPos() );
        break;
        case CSVCMD_SETPOSOFFSET:
            ImplSetPosOffset( nParam1 );
        break;
        case CSVCMD_SETHDRWIDTH:
            maData.mnHdrWidth = std::max( nParam1, sal_Int32( 0 ) );
            ImplSetPosOffset( mxGrid->GetFirstVisPos() );
        break;
        case CSVCMD_SETCHARWIDTH:
            maData.mnCharWidth = std::max( nParam1, sal_Int32( 1 ) );
            ImplSetPosOffset( mxGrid->GetFirstVisPos() );
        break;
        case CSVCMD_SETLINECOUNT:
            maData.mnLineCount = std::max( nParam1, sal_Int32( 1 ) );
            ImplSetLineOffset( mxGrid->GetFirstVisLine() );
        break;
        case CSVCMD_SETLINEOFFSET:
            ImplSetLineOffset( nParam1 );
        break;
        case CSVCMD_SETHDRHEIGHT:
            maData.mnHdrHeight = std::max( nParam1, sal_Int32( 0 ) );
            ImplSetLineOffset( mxGrid->GetFirstVisLine() );
        break;
        case CSVCMD_SETLINEHEIGHT:
            maData.mnLineHeight = std::max( nParam1, sal_Int32( 1 ) );
            ImplSetLineOffset( mxGrid->GetFirstVisLine() );
        break;
        case CSVCMD_MOVERULERCURSOR:
            maData.mnPosCursor = mxGrid->IsVisibleSplitPos( nParam1 ) ? nParam1 : CSV_POS_INVALID;
        break;
        case CSVCMD_MOVEGRIDCURSOR:
            maData.mnColCursor = ((0 <= nParam1) && (nParam1 < mxGrid->GetPosCount())) ? nParam1 : CSV_POS_INVALID;
        break;
        default:
        {
            // added to avoid warnings
        }
    }

    if( maData != aOldData )
    {
        mxGrid->DisableRepaint();
        mxRuler->ApplyLayout( aOldData );
        mxGrid->ApplyLayout( aOldData );
        mxGrid->EnableRepaint();
    }
}

IMPL_LINK(ScCsvTableBox, HScrollHdl, weld::ScrolledWindow&, rScroll, void)
{
    int nLower = 0;
    int nValue = rScroll.hadjustment_get_value();
    int nUpper = mxGrid->GetPosCount() + 2;
    int nPageSize = mxGrid->GetVisPosCount();

    // Undo scrollbar RTL
    if (AllSettings::GetLayoutRTL())
        nValue = nUpper - (nValue - nLower + nPageSize);

    mxGrid->Execute(CSVCMD_SETPOSOFFSET, nValue);
    maEndScrollIdle.Start();
}

IMPL_LINK(ScCsvTableBox, VScrollHdl, weld::ScrolledWindow&, rScroll, void)
{
    mxGrid->Execute(CSVCMD_SETLINEOFFSET, rScroll.vadjustment_get_value());
}

IMPL_LINK_NOARG(ScCsvTableBox, ScrollEndHdl, Timer*, void)
{
    if( mxGrid->GetRulerCursorPos() != CSV_POS_INVALID )
        mxGrid->Execute( CSVCMD_MOVERULERCURSOR, mxRuler->GetNoScrollPos( mxGrid->GetRulerCursorPos() ) );
    if( mxGrid->GetGridCursorPos() != CSV_POS_INVALID )
        mxGrid->Execute( CSVCMD_MOVEGRIDCURSOR, mxGrid->GetNoScrollCol( mxGrid->GetGridCursorPos() ) );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
