/* -*- 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 "svtools/controldims.hrc"

#include "RemoteDialogClientBox.hxx"
#include "RemoteServer.hxx"

#include "comphelper/processfactory.hxx"
#include "com/sun/star/i18n/CollatorOptions.hpp"
#include "com/sun/star/deployment/DependencyException.hpp"
#include "com/sun/star/deployment/DeploymentException.hpp"
#include <vcl/settings.hxx>

#include "glob.hrc"

using namespace ::com::sun::star;

namespace sd {


//                          struct ClientBoxEntry

ClientBoxEntry::ClientBoxEntry( ClientInfo* pClientInfo ) :
    m_bActive( false ),
    m_pClientInfo( pClientInfo )
{
}


ClientBoxEntry::~ClientBoxEntry()
{}



// ClientRemovedListener

void ClientRemovedListener::disposing( lang::EventObject const & rEvt )
    throw ( uno::RuntimeException, std::exception )
{
    (void) rEvt;
}


ClientRemovedListener::~ClientRemovedListener()
{
}


// ClientBox

ClientBox::ClientBox( Window* pParent, WinBits nStyle ) :
    Control( pParent, nStyle ),
    m_bHasScrollBar( false ),
    m_bHasActive( false ),
    m_bNeedsRecalc( true ),
    m_bInCheckMode( false ),
    m_bAdjustActive( false ),
    m_bInDelete( false ),
    m_nActive( 0 ),
    m_nTopIndex( 0 ),
    m_nActiveHeight( 0 ),
    m_nExtraHeight( 2 ),
    m_aPinBox( this, 0 ),
    m_aScrollBar( this, WB_VERT )
{
    m_aScrollBar.SetScrollHdl( LINK( this, ClientBox, ScrollHdl ) );
    m_aScrollBar.EnableDrag();

    m_aPinBox.SetUseThousandSep(false);

    SetPaintTransparent( true );
    SetPosPixel( Point( RSC_SP_DLG_INNERBORDER_LEFT, RSC_SP_DLG_INNERBORDER_TOP ) );
    long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
    long nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
    if ( nIconHeight < nTitleHeight )
        m_nStdHeight = nTitleHeight;
    else
        m_nStdHeight = nIconHeight;
    m_nStdHeight += GetTextHeight() + TOP_OFFSET;

//     nIconHeight = ICON_HEIGHT + 2*TOP_OFFSET + 1;
//     if ( m_nStdHeight < nIconHeight )
//         m_nStdHeight = nIconHeight;

    m_nActiveHeight = m_nStdHeight;

    const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
    if( IsControlBackground() )
        SetBackground( GetControlBackground() );
    else
        SetBackground( rStyleSettings.GetFieldColor() );

    m_xRemoveListener = new ClientRemovedListener( this );

    Show();
}

extern "C" SAL_DLLPUBLIC_EXPORT Window* SAL_CALL makeClientBox(Window *pParent, VclBuilder::stringmap &rMap)
{
    WinBits nWinStyle = WB_TABSTOP;
    OString sBorder = VclBuilder::extractCustomProperty(rMap);
    if (!sBorder.isEmpty())
        nWinStyle |= WB_BORDER;
    return new ClientBox(pParent, nWinStyle);
}

Size ClientBox::GetOptimalSize() const
{
    return LogicToPixel(Size(200, 140), MAP_APPFONT);
}


ClientBox::~ClientBox()
{
    if ( ! m_bInDelete )
        DeleteRemoved();

    m_bInDelete = true;

    typedef std::vector< TClientBoxEntry >::iterator ITER;

    for ( ITER iIndex = m_vEntries.begin(); iIndex < m_vEntries.end(); ++iIndex )
    {
//         (*iIndex)->m_xPackage->removeEventListener( uno::Reference< lang::XEventListener > ( m_xRemoveListener, uno::UNO_QUERY ) );
    }

    m_vEntries.clear();

    m_xRemoveListener.clear();
}


// Title + description
void ClientBox::CalcActiveHeight( const long nPos )
{
    (void) nPos;
    const ::osl::MutexGuard aGuard( m_entriesMutex );

    // get title height
    long aTextHeight;
    long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
    long nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
    if ( nIconHeight < nTitleHeight )
        aTextHeight = nTitleHeight;
    else
        aTextHeight = nIconHeight;

    // Text entry height
    Size aSize = GetOutputSizePixel();
    if ( m_bHasScrollBar )
        aSize.Width() -= m_aScrollBar.GetSizePixel().Width();

    aSize.Width() -= ICON_OFFSET;

    aSize = LogicToPixel( Size( RSC_CD_PUSHBUTTON_WIDTH, RSC_CD_PUSHBUTTON_HEIGHT ),
                               MapMode( MAP_APPFONT ) );
    aTextHeight += aSize.Height();

    if ( aTextHeight < m_nStdHeight )
        aTextHeight = m_nStdHeight;

    m_nActiveHeight = aTextHeight + 2;
}


Rectangle ClientBox::GetEntryRect( const long nPos ) const
{
    const ::osl::MutexGuard aGuard( m_entriesMutex );

    Size aSize( GetOutputSizePixel() );

    if ( m_bHasScrollBar )
        aSize.Width() -= m_aScrollBar.GetSizePixel().Width();

    if ( m_vEntries[ nPos ]->m_bActive )
        aSize.Height() = m_nActiveHeight;
    else
        aSize.Height() = m_nStdHeight;

    Point aPos( 0, -m_nTopIndex + nPos * m_nStdHeight );
    if ( m_bHasActive && ( nPos < m_nActive ) )
        aPos.Y() += m_nActiveHeight - m_nStdHeight;

    return Rectangle( aPos, aSize );
}


void ClientBox::DeleteRemoved()
{
    const ::osl::MutexGuard aGuard( m_entriesMutex );

    m_bInDelete = true;

    if ( ! m_vRemovedEntries.empty() )
    {
        m_vRemovedEntries.clear();
    }

    m_bInDelete = false;
}

long ClientBox::GetActiveEntryIndex()
{
    if ( m_bHasActive )
        return m_nActive;
    else
        return -1;
}


//This function may be called with nPos < 0
void ClientBox::selectEntry( const long nPos )
{
    //ToDo whe should not use the guard at such a big scope here.
    //Currently it is used to guard m_vEntries and m_nActive. m_nActive will be
    //modified in this function.
    //It would be probably best to always use a copy of m_vEntries
    //and some other state variables from ClientBox for
    //the whole painting operation. See issue i86993
    ::osl::ClearableMutexGuard guard(m_entriesMutex);

    if ( m_bInCheckMode )
        return;

    if ( m_bHasActive )
    {
        if ( nPos == m_nActive )
            return;

        m_bHasActive = false;
        m_vEntries[ m_nActive ]->m_bActive = false;
    }

    if ( ( nPos >= 0 ) && ( nPos < (long) m_vEntries.size() ) )
    {
        m_bHasActive = true;
        m_nActive = nPos;
        m_vEntries[ nPos ]->m_bActive = true;

        if ( IsReallyVisible() )
        {
            m_bAdjustActive = true;
        }
    }

    if ( IsReallyVisible() )
    {
        m_bNeedsRecalc = true;
        Invalidate();
    }

    guard.clear();
}


void ClientBox::DrawRow( const Rectangle& rRect, const TClientBoxEntry pEntry )
{
    const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();

    if ( pEntry->m_bActive )
        SetTextColor( rStyleSettings.GetHighlightTextColor() );
    else
        SetTextColor( rStyleSettings.GetFieldTextColor() );

    if ( pEntry->m_bActive )
    {
        SetLineColor();
        SetFillColor( rStyleSettings.GetHighlightColor() );
        DrawRect( rRect );
    }
    else
    {
        if( IsControlBackground() )
            SetBackground( GetControlBackground() );
        else
            SetBackground( rStyleSettings.GetFieldColor() );

        SetTextFillColor();
        Erase( rRect );
    }

    // FIXME: draw bluetooth or wifi icon
     Point aPos( rRect.TopLeft() );
//     aPos += Point( TOP_OFFSET, TOP_OFFSET );
//     Image aImage;
//     if ( ! pEntry->m_aIcon )
//         aImage = m_aDefaultImage;
//     else
//         aImage = pEntry->m_aIcon;
//     Size aImageSize = aImage.GetSizePixel();
//     if ( ( aImageSize.Width() <= ICON_WIDTH ) && ( aImageSize.Height() <= ICON_HEIGHT ) )
//         DrawImage( Point( aPos.X()+((ICON_WIDTH-aImageSize.Width())/2), aPos.Y()+((ICON_HEIGHT-aImageSize.Height())/2) ), aImage );
//     else
//         DrawImage( aPos, Size( ICON_WIDTH, ICON_HEIGHT ), aImage );

    // Setup fonts
    Font aStdFont( GetFont() );
    Font aBoldFont( aStdFont );
    aBoldFont.SetWeight( WEIGHT_BOLD );
    SetFont( aBoldFont );
    long aTextHeight = GetTextHeight();

    // Get max title width
    long nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET;
    nMaxTitleWidth -= ( 2 * SMALL_ICON_SIZE ) + ( 4 * SPACE_BETWEEN );

    long aTitleWidth = GetTextWidth( pEntry->m_pClientInfo->mName ) + (aTextHeight / 3);

    aPos = rRect.TopLeft() + Point( ICON_OFFSET, TOP_OFFSET );

    if ( aTitleWidth > nMaxTitleWidth )
    {
        aTitleWidth = nMaxTitleWidth - (aTextHeight / 3);
        OUString aShortTitle = GetEllipsisString( pEntry->m_pClientInfo->mName,
                                                  aTitleWidth );
        DrawText( aPos, aShortTitle );
        aTitleWidth += (aTextHeight / 3);
    }
    else
        DrawText( aPos, pEntry->m_pClientInfo->mName );

    SetFont( aStdFont );

    aPos.Y() += aTextHeight;
    if ( pEntry->m_bActive )
    {
        Size aSize = LogicToPixel( Size( RSC_CD_PUSHBUTTON_WIDTH, RSC_CD_PUSHBUTTON_HEIGHT ),
                               MapMode( MAP_APPFONT ) );
        m_aPinBox.SetSizePixel( aSize );
        const Rectangle aRect( GetEntryRect( m_nActive ) );
        Size  aBtnSize( m_aPinBox.GetSizePixel() );
        Point aBtnPos( aRect.Left(),
                   aRect.Bottom() - TOP_OFFSET - aBtnSize.Height() );
        OUString sPinText(SD_RESSTR(STR_ENTER_PIN));
        DrawText( Rectangle( aBtnPos.X(), aBtnPos.Y(), rRect.Right(), rRect.Bottom() - TOP_OFFSET),
                  sPinText, 0 );

        aBtnPos = Point( aRect.Left() + GetTextWidth( sPinText ),
                   aRect.Bottom() - TOP_OFFSET - aBtnSize.Height() );

        m_aPinBox.SetPosPixel( aBtnPos );





//         long nExtraHeight = 0;

//         if ( pEntry->m_bHasButtons )
//             nExtraHeight = m_nExtraHeight;

//         DrawText( Rectangle( aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight ),
//                   sDescription, TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK );
    }
    else
    {
    }

    SetLineColor( Color( COL_LIGHTGRAY ) );
    DrawLine( rRect.BottomLeft(), rRect.BottomRight() );
}


void ClientBox::RecalcAll()
{
    if ( m_bHasActive )
        CalcActiveHeight( m_nActive );

    SetupScrollBar();

    if ( m_bHasActive )
    {
        Rectangle aEntryRect = GetEntryRect( m_nActive );

        if ( m_bAdjustActive )
        {
            m_bAdjustActive = false;

            // If the top of the selected entry isn't visible, make it visible
            if ( aEntryRect.Top() < 0 )
            {
                m_nTopIndex += aEntryRect.Top();
                aEntryRect.Move( 0, -aEntryRect.Top() );
            }

            // If the bottom of the selected entry isn't visible, make it visible even if now the top
            // isn't visible any longer ( the buttons are more important )
            Size aOutputSize = GetOutputSizePixel();
            if ( aEntryRect.Bottom() > aOutputSize.Height() )
            {
                m_nTopIndex += ( aEntryRect.Bottom() - aOutputSize.Height() );
                aEntryRect.Move( 0, -( aEntryRect.Bottom() - aOutputSize.Height() ) );
            }

            // If there is unused space below the last entry but all entries don't fit into the box,
            // move the content down to use the whole space
            const long nTotalHeight = GetTotalHeight();
            if ( m_bHasScrollBar && ( aOutputSize.Height() + m_nTopIndex > nTotalHeight ) )
            {
                long nOffset = m_nTopIndex;
                m_nTopIndex = nTotalHeight - aOutputSize.Height();
                nOffset -= m_nTopIndex;
                aEntryRect.Move( 0, nOffset );
            }

            if ( m_bHasScrollBar )
                m_aScrollBar.SetThumbPos( m_nTopIndex );
        }
    }

    m_bNeedsRecalc = false;
}


bool ClientBox::HandleTabKey( bool )
{
    return false;
}


bool ClientBox::HandleCursorKey( sal_uInt16 nKeyCode )
{
    if ( m_vEntries.empty() )
        return true;

    long nSelect = 0;

    if ( m_bHasActive )
    {
        long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight;
        if ( nPageSize < 2 )
            nPageSize = 2;

        if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_RIGHT ) )
            nSelect = m_nActive + 1;
        else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_LEFT ) )
            nSelect = m_nActive - 1;
        else if ( nKeyCode == KEY_HOME )
            nSelect = 0;
        else if ( nKeyCode == KEY_END )
            nSelect = m_vEntries.size() - 1;
        else if ( nKeyCode == KEY_PAGEUP )
            nSelect = m_nActive - nPageSize + 1;
        else if ( nKeyCode == KEY_PAGEDOWN )
            nSelect = m_nActive + nPageSize - 1;
    }
    else // when there is no selected entry, we will select the first or the last.
    {
        if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_PAGEDOWN ) || ( nKeyCode == KEY_HOME ) )
            nSelect = 0;
        else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_PAGEUP ) || ( nKeyCode == KEY_END ) )
            nSelect = m_vEntries.size() - 1;
    }

    if ( nSelect < 0 )
        nSelect = 0;
    if ( nSelect >= (long) m_vEntries.size() )
        nSelect = m_vEntries.size() - 1;

    selectEntry( nSelect );

    return true;
}


void ClientBox::Paint( const Rectangle &/*rPaintRect*/ )
{
    if ( !m_bInDelete )
        DeleteRemoved();

    if ( m_bNeedsRecalc )
        RecalcAll();

    Point aStart( 0, -m_nTopIndex );
    Size aSize( GetOutputSizePixel() );

    if ( m_bHasScrollBar )
        aSize.Width() -= m_aScrollBar.GetSizePixel().Width();

    const ::osl::MutexGuard aGuard( m_entriesMutex );

    typedef std::vector< TClientBoxEntry >::iterator ITER;
    for ( ITER iIndex = m_vEntries.begin(); iIndex < m_vEntries.end(); ++iIndex )
    {
        aSize.Height() = (*iIndex)->m_bActive ? m_nActiveHeight : m_nStdHeight;
        Rectangle aEntryRect( aStart, aSize );
        DrawRow( aEntryRect, *iIndex );
        aStart.Y() += aSize.Height();
    }
}


long ClientBox::GetTotalHeight() const
{
    long nHeight = m_vEntries.size() * m_nStdHeight;

    if ( m_bHasActive )
    {
        nHeight += m_nActiveHeight - m_nStdHeight;
    }

    return nHeight;
}


void ClientBox::SetupScrollBar()
{
    const Size aSize = GetOutputSizePixel();
    const long nScrBarSize = GetSettings().GetStyleSettings().GetScrollBarSize();
    const long nTotalHeight = GetTotalHeight();
    const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() );

    if ( bNeedsScrollBar )
    {
        if ( m_nTopIndex + aSize.Height() > nTotalHeight )
            m_nTopIndex = nTotalHeight - aSize.Height();

        m_aScrollBar.SetPosSizePixel( Point( aSize.Width() - nScrBarSize, 0 ),
                                       Size( nScrBarSize, aSize.Height() ) );
        m_aScrollBar.SetRangeMax( nTotalHeight );
        m_aScrollBar.SetVisibleSize( aSize.Height() );
        m_aScrollBar.SetPageSize( ( aSize.Height() * 4 ) / 5 );
        m_aScrollBar.SetLineSize( m_nStdHeight );
        m_aScrollBar.SetThumbPos( m_nTopIndex );

        if ( !m_bHasScrollBar )
            m_aScrollBar.Show();
    }
    else if ( m_bHasScrollBar )
    {
        m_aScrollBar.Hide();
        m_nTopIndex = 0;
    }

    m_bHasScrollBar = bNeedsScrollBar;
}


void ClientBox::Resize()
{
    RecalcAll();
}


long ClientBox::PointToPos( const Point& rPos )
{
    long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight;

    if ( m_bHasActive && ( nPos > m_nActive ) )
    {
        if ( rPos.Y() + m_nTopIndex <= m_nActive*m_nStdHeight + m_nActiveHeight )
            nPos = m_nActive;
        else
            nPos = ( rPos.Y() + m_nTopIndex - (m_nActiveHeight - m_nStdHeight) ) / m_nStdHeight;
    }

    return nPos;
}

OUString ClientBox::getPin()
{
    return OUString::number( m_aPinBox.GetValue() );
}


void ClientBox::MouseButtonDown( const MouseEvent& rMEvt )
{
    long nPos = PointToPos( rMEvt.GetPosPixel() );

    if ( rMEvt.IsLeft() )
    {
        if ( rMEvt.IsMod1() && m_bHasActive )
            selectEntry( m_vEntries.size() );   // Selecting an not existing entry will deselect the current one
        else
            selectEntry( nPos );
    }
}


bool ClientBox::Notify( NotifyEvent& rNEvt )
{
    if ( !m_bInDelete )
        DeleteRemoved();

    bool bHandled = false;

    if ( rNEvt.GetType() == EVENT_KEYINPUT )
    {
        const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
        KeyCode         aKeyCode = pKEvt->GetKeyCode();
        sal_uInt16          nKeyCode = aKeyCode.GetCode();

        if ( nKeyCode == KEY_TAB )
            bHandled = HandleTabKey( aKeyCode.IsShift() );
        else if ( aKeyCode.GetGroup() == KEYGROUP_CURSOR )
            bHandled = HandleCursorKey( nKeyCode );
    }

    if ( rNEvt.GetType() == EVENT_COMMAND )
    {
        if ( m_bHasScrollBar &&
             ( rNEvt.GetCommandEvent()->GetCommand() == COMMAND_WHEEL ) )
        {
            const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
            if ( pData->GetMode() == COMMAND_WHEEL_SCROLL )
            {
                long nThumbPos = m_aScrollBar.GetThumbPos();
                if ( pData->GetDelta() < 0 )
                    m_aScrollBar.DoScroll( nThumbPos + m_nStdHeight );
                else
                    m_aScrollBar.DoScroll( nThumbPos - m_nStdHeight );
                bHandled = true;
            }
        }
    }

    if ( !bHandled )
        return Control::Notify( rNEvt );
    else
        return true;
}



long ClientBox::addEntry( ClientInfo* pClientInfo )
{
    long         nPos = 0;
//     PackageState eState = m_pManager->getPackageState( xPackage );
//     bool         bLocked = m_pManager->isReadOnly( xPackage );

    TClientBoxEntry pEntry( new ClientBoxEntry( pClientInfo ) );

    bool bNewEntryInserted = false;

    ::osl::ClearableMutexGuard guard(m_entriesMutex);
    if ( m_vEntries.empty() )
    {
        m_vEntries.push_back( pEntry );
        bNewEntryInserted = true;
    }
    else
    {
//         if ( !FindEntryPos( pEntry, 0, m_vEntries.size()-1, nPos ) )
//         {
            m_vEntries.insert( m_vEntries.begin()+nPos, pEntry );
            bNewEntryInserted = true;
//         }
//         else if ( !m_bInCheckMode )
//         {
//             OSL_FAIL( "ClientBox::addEntry(): Will not add duplicate entries"  );
//         }
    }

    //Related: rhbz#702833 Only add a Listener if we're adding a new entry, to
    //keep in sync with removeEventListener logic
    if (bNewEntryInserted)
    {

        //         pEntry->m_xPackage->addEventListener(uno::Reference< lang::XEventListener > ( m_xRemoveListener, uno::UNO_QUERY ) );
    }


//     pEntry->m_bHasOptions = m_pManager->supportsOptions( xPackage );
//     pEntry->m_bUser       = xPackage->getRepositoryName().equals( USER_PACKAGE_MANAGER );
//     pEntry->m_bShared     = xPackage->getRepositoryName().equals( SHARED_PACKAGE_MANAGER );
//     pEntry->m_bNew        = m_bInCheckMode;
//     pEntry->m_bMissingLic = bLicenseMissing;

//     if ( bLicenseMissing )
//         pEntry->m_sErrorText = DialogHelper::getResourceString( RID_STR_ERROR_MISSING_LICENSE );

    //access to m_nActive must be guarded
    if ( !m_bInCheckMode && m_bHasActive && ( m_nActive >= nPos ) )
        m_nActive += 1;

    guard.clear();

    if ( IsReallyVisible() )
        Invalidate();

    m_bNeedsRecalc = true;

    return nPos;
}


void ClientBox::DoScroll( long nDelta )
{
    m_nTopIndex += nDelta;
    Point aNewSBPt( m_aScrollBar.GetPosPixel() );

    Rectangle aScrRect( Point(), GetOutputSizePixel() );
    aScrRect.Right() -= m_aScrollBar.GetSizePixel().Width();
    Scroll( 0, -nDelta, aScrRect );

    m_aScrollBar.SetPosPixel( aNewSBPt );
}


IMPL_LINK( ClientBox, ScrollHdl, ScrollBar*, pScrBar )
{
    DoScroll( pScrBar->GetDelta() );

    return 1;
}

} //namespace dp_gui

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