/* -*- 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 <comphelper/lok.hxx>
#include <sal/log.hxx>

#include <headless/svpframe.hxx>
#include <headless/svpinst.hxx>
#ifndef IOS
#include <headless/svpgdi.hxx>
#endif
#include <salsys.hxx>

#include <basegfx/vector/b2ivector.hxx>

#ifndef IOS
#include <cairo.h>
#endif

SvpSalFrame* SvpSalFrame::s_pFocusFrame = nullptr;

#ifdef IOS
#define SvpSalGraphics AquaSalGraphics
#endif

SvpSalFrame::SvpSalFrame( SvpSalInstance* pInstance,
                          SalFrame* pParent,
                          SalFrameStyleFlags nSalFrameStyle ) :
    m_pInstance( pInstance ),
    m_pParent( static_cast<SvpSalFrame*>(pParent) ),
    m_nStyle( nSalFrameStyle ),
    m_bVisible( false ),
#ifndef IOS
    m_pSurface( nullptr ),
#endif
    m_nMinWidth( 0 ),
    m_nMinHeight( 0 ),
    m_nMaxWidth( 0 ),
    m_nMaxHeight( 0 )
{
#if !defined(IOS) && !defined(MACOSX) && !defined(ANDROID)
    m_aSystemChildData.pSalFrame    = this;
#endif

    if( m_pParent )
        m_pParent->m_aChildren.push_back( this );

    if( m_pInstance )
        m_pInstance->registerFrame( this );

    SetPosSize( 0, 0, 800, 600, SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
}

SvpSalFrame::~SvpSalFrame()
{
    if( m_pInstance )
        m_pInstance->deregisterFrame( this );

    std::vector<SvpSalFrame*> Children = m_aChildren;
    for( auto& rChild : Children )
        rChild->SetParent( m_pParent );
    if( m_pParent )
        std::erase(m_pParent->m_aChildren, this);

    if( s_pFocusFrame == this )
    {
        s_pFocusFrame = nullptr;
        // call directly here, else an event for a destroyed frame would be dispatched
        CallCallback( SalEvent::LoseFocus, nullptr );
        // if the handler has not set a new focus frame
        // pass focus to another frame, preferably a document style window
        if( s_pFocusFrame == nullptr )
        {
            for (auto pSalFrame : m_pInstance->getFrames() )
            {
                SvpSalFrame* pFrame = static_cast<SvpSalFrame*>( pSalFrame );
                if( pFrame->m_bVisible        &&
                    pFrame->m_pParent == nullptr &&
                    (pFrame->m_nStyle & (SalFrameStyleFlags::MOVEABLE |
                                         SalFrameStyleFlags::SIZEABLE |
                                         SalFrameStyleFlags::CLOSEABLE) )
                    )
                {
                    pFrame->GetFocus();
                    break;
                }
            }
        }
    }
#ifndef IOS
    if (m_pSurface)
        cairo_surface_destroy(m_pSurface);
#endif
}

void SvpSalFrame::GetFocus()
{
    if (m_nStyle == SalFrameStyleFlags::NONE)
        return;
    if( s_pFocusFrame == this )
        return;
    // FIXME: return if !m_bVisible
    // That's IMHO why CppunitTest_sd_tiledrendering crashes non-headless

    if( (m_nStyle & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::FLOAT)) == SalFrameStyleFlags::NONE )
    {
        if( s_pFocusFrame )
            s_pFocusFrame->LoseFocus();
        s_pFocusFrame = this;
        m_pInstance->PostEvent( this, nullptr, SalEvent::GetFocus );
    }
}

void SvpSalFrame::LoseFocus()
{
    if( s_pFocusFrame == this )
    {
        m_pInstance->PostEvent( this, nullptr, SalEvent::LoseFocus );
        s_pFocusFrame = nullptr;
    }
}

basegfx::B2IVector SvpSalFrame::GetSurfaceFrameSize() const
{
    basegfx::B2IVector aFrameSize( maGeometry.width(), maGeometry.height() );
    if( aFrameSize.getX() == 0 )
        aFrameSize.setX( 1 );
    if( aFrameSize.getY() == 0 )
        aFrameSize.setY( 1 );
    // Creating backing surfaces for invisible windows costs a big chunk of RAM.
    if (Application::IsHeadlessModeEnabled())
         aFrameSize = basegfx::B2IVector( 1, 1 );
    return aFrameSize;
}

SalGraphics* SvpSalFrame::AcquireGraphics()
{
    SvpSalGraphics* pGraphics = new SvpSalGraphics();
#ifndef IOS
    pGraphics->setSurface(m_pSurface, GetSurfaceFrameSize());
#endif
    m_aGraphics.push_back( pGraphics );
    return pGraphics;
}

void SvpSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
{
    SvpSalGraphics* pSvpGraphics = dynamic_cast<SvpSalGraphics*>(pGraphics);
    std::erase(m_aGraphics, pSvpGraphics);
    delete pSvpGraphics;
}

bool SvpSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
{
    m_pInstance->PostEvent( this, pData.release(), SalEvent::UserEvent );
    return true;
}

void SvpSalFrame::PostPaint() const
{
    if( m_bVisible )
    {
        SalPaintEvent aPEvt(0, 0, maGeometry.width(), maGeometry.height());
        aPEvt.mbImmediateUpdate = false;
        CallCallback( SalEvent::Paint, &aPEvt );
    }
}

void SvpSalFrame::SetTitle(const OUString& sTitle)
{
    m_sTitle = sTitle;
}

void SvpSalFrame::SetIcon( sal_uInt16 )
{
}

void SvpSalFrame::SetMenu( SalMenu* )
{
}

void SvpSalFrame::SetExtendedFrameStyle( SalExtStyle )
{
}

void SvpSalFrame::Show( bool bVisible, bool bNoActivate )
{
    if (m_nStyle == SalFrameStyleFlags::NONE)
        return;
    if (bVisible == m_bVisible)
    {
        if (m_bVisible && !bNoActivate)
            GetFocus();
        return;
    }

    if (bVisible)
    {
        m_bVisible = true;
        m_pInstance->PostEvent( this, nullptr, SalEvent::Resize );
        if( ! bNoActivate )
            GetFocus();
    }
    else
    {
        m_bVisible = false;
        LoseFocus();
    }
}

void SvpSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
{
    m_nMinWidth = nWidth;
    m_nMinHeight = nHeight;
}

void SvpSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
{
    m_nMaxWidth = nWidth;
    m_nMaxHeight = nHeight;
}

void SvpSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
{
    if( (nFlags & SAL_FRAME_POSSIZE_X) != 0 )
        maGeometry.setX(nX);
    if( (nFlags & SAL_FRAME_POSSIZE_Y) != 0 )
        maGeometry.setY(nY);
    if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 )
    {
        maGeometry.setWidth(nWidth);
        if (m_nMaxWidth > 0 && maGeometry.width() > m_nMaxWidth)
            maGeometry.setWidth(m_nMaxWidth);
        if (m_nMinWidth > 0 && maGeometry.width() < m_nMinWidth)
            maGeometry.setWidth(m_nMinWidth);
    }
    if( (nFlags & SAL_FRAME_POSSIZE_HEIGHT) != 0 )
    {
        maGeometry.setHeight(nHeight);
        if (m_nMaxHeight > 0 && maGeometry.height() > m_nMaxHeight)
            maGeometry.setHeight(m_nMaxHeight);
        if (m_nMinHeight > 0 && maGeometry.height() < m_nMinHeight)
            maGeometry.setHeight(m_nMinHeight);
    }
#ifndef IOS
    basegfx::B2IVector aFrameSize = GetSurfaceFrameSize();
    if (!m_pSurface || cairo_image_surface_get_width(m_pSurface) != aFrameSize.getX() ||
                       cairo_image_surface_get_height(m_pSurface) != aFrameSize.getY() )
    {
        if (m_pSurface)
            cairo_surface_destroy(m_pSurface);

        m_pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
                                                aFrameSize.getX(),
                                                aFrameSize.getY());

        // update device in existing graphics
        for (auto const& graphic : m_aGraphics)
        {
             graphic->setSurface(m_pSurface, aFrameSize);
        }
    }
    if( m_bVisible )
        m_pInstance->PostEvent( this, nullptr, SalEvent::Resize );
#endif
}

void SvpSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
{
    rWidth = maGeometry.width();
    rHeight = maGeometry.height();
}

void SvpSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
{
    rRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( 0, 0 ),
                       AbsoluteScreenPixelSize( VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT ) );
}

SalFrame* SvpSalFrame::GetParent() const
{
    return m_pParent;
}

void SvpSalFrame::SetWindowState(const vcl::WindowData *pState)
{
    if (pState == nullptr)
        return;

    // Request for position or size change
    if (!(pState->mask() & vcl::WindowDataMask::PosSize))
        return;

    tools::Long nX = maGeometry.x();
    tools::Long nY = maGeometry.y();
    tools::Long nWidth = maGeometry.width();
    tools::Long nHeight = maGeometry.height();

    // change requested properties
    if (pState->mask() & vcl::WindowDataMask::X)
        nX = pState->x();
    if (pState->mask() & vcl::WindowDataMask::Y)
        nY = pState->y();
    if (pState->mask() & vcl::WindowDataMask::Width)
        nWidth = pState->width();
    if (pState->mask() & vcl::WindowDataMask::Height)
        nHeight = pState->height();

    SetPosSize( nX, nY, nWidth, nHeight,
                SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y |
                SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
}

bool SvpSalFrame::GetWindowState(vcl::WindowData* pState)
{
    pState->setPosSize(maGeometry.posSize());
    pState->setState(vcl::WindowState::Normal);
    pState->setMask(vcl::WindowDataMask::PosSizeState);
    return true;
}

void SvpSalFrame::ShowFullScreen( bool, sal_Int32 )
{
    SetPosSize( 0, 0, VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT,
                SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
}

void SvpSalFrame::StartPresentation( bool )
{
}

void SvpSalFrame::SetAlwaysOnTop( bool )
{
}

void SvpSalFrame::ToTop(SalFrameToTop nFlags)
{
    if (m_nStyle == SalFrameStyleFlags::NONE)
        return;
    if (nFlags & SalFrameToTop::RestoreWhenMin)
        Show(true, false);
    else
        GetFocus();
}

void SvpSalFrame::SetPointer( PointerStyle )
{
}

void SvpSalFrame::CaptureMouse( bool )
{
}

void SvpSalFrame::SetPointerPos( tools::Long, tools::Long )
{
}

void SvpSalFrame::Flush()
{
}

void SvpSalFrame::SetInputContext( SalInputContext* )
{
}

void SvpSalFrame::EndExtTextInput( EndExtTextInputFlags )
{
}

OUString SvpSalFrame::GetKeyName(sal_uInt16 nKeyCode)
{
    // Skip key combinations with any modifiers that we don’t support
    if ((nKeyCode & KEY_MODIFIERS_MASK & ~(KEY_SHIFT | KEY_MOD1 | KEY_MOD2)) != 0)
        return OUString();

    OUStringBuffer aResult;

    if ((nKeyCode & KEY_MOD1) != 0)
        aResult.append("Ctrl+");
    if ((nKeyCode & KEY_MOD2) != 0)
        aResult.append("Alt+");
    if ((nKeyCode & KEY_SHIFT) != 0)
        aResult.append("Shift+");

    static const struct
    {
        sal_uInt16 code;
        const char* name;
    } keyMap[] = {
        { KEY_DOWN, "Down" },
        { KEY_UP, "Up" },
        { KEY_LEFT, "Left" },
        { KEY_RIGHT, "Right" },
        { KEY_HOME, "Home" },
        { KEY_END, "End" },
        { KEY_PAGEUP, "PgUp" },
        { KEY_PAGEDOWN, "PgDown" },
        { KEY_RETURN, "Ret" },
        { KEY_ESCAPE, "Esc" },
        { KEY_TAB, "Tab" },
        { KEY_BACKSPACE, "BkSpace" },
        { KEY_SPACE, "Space" },
        { KEY_DELETE, "Del" },
        { KEY_ADD, "+" },
        { KEY_SUBTRACT, "-" },
        { KEY_DIVIDE, "/" },
        { KEY_MULTIPLY, "*" },
        { KEY_POINT, "." },
        { KEY_COMMA, "," },
        { KEY_LESS, "<" },
        { KEY_GREATER, ">" },
        { KEY_EQUAL, "=" },
        { KEY_TILDE, "~" },
        { KEY_BRACKETLEFT, "[" },
        { KEY_BRACKETRIGHT, "]" },
        { KEY_SEMICOLON, ";" },
        { KEY_QUOTERIGHT, "'" },
        { KEY_RIGHTCURLYBRACKET, "}" },
        { KEY_NUMBERSIGN, "#" },
        { KEY_COLON, ":" },
    };

    sal_uInt16 nUnmodifiedCode = (nKeyCode & KEY_CODE_MASK);

    if (nUnmodifiedCode >= KEY_A && nUnmodifiedCode <= KEY_Z)
        aResult.append(char('A' + nUnmodifiedCode - KEY_A));
    else if (nUnmodifiedCode >= KEY_F1 && nUnmodifiedCode <= KEY_F26)
        aResult.append(OUStringChar('F') + OUString::number(nUnmodifiedCode - KEY_F1 + 1));
    else if (nUnmodifiedCode >= KEY_0 && nUnmodifiedCode <= KEY_9)
        aResult.append(char('0' + nUnmodifiedCode - KEY_0));
    else if (auto it = std::find_if(keyMap, keyMap + std::size(keyMap),
                                    [=](const auto& p) { return p.code == nUnmodifiedCode; });
             it != keyMap + std::size(keyMap))
        aResult.appendAscii(it->name);
    else
        return OUString();

    return aResult.makeStringAndClear();
}

bool SvpSalFrame::MapUnicodeToKeyCode( sal_Unicode, LanguageType, vcl::KeyCode& )
{
    return false;
}

LanguageType SvpSalFrame::GetInputLanguage()
{
    return LANGUAGE_DONTKNOW;
}

void SvpSalFrame::UpdateSettings( AllSettings& rSettings )
{
    StyleSettings aStyleSettings = rSettings.GetStyleSettings();

    Color aBackgroundColor( 0xef, 0xef, 0xef );
    aStyleSettings.BatchSetBackgrounds( aBackgroundColor, false );
    aStyleSettings.SetMenuColor( aBackgroundColor );
    aStyleSettings.SetMenuBarColor( aBackgroundColor );

    if (comphelper::LibreOfficeKit::isActive()) // TODO: remove this.
    {
        vcl::Font aStdFont( FAMILY_SWISS, Size( 0, 14 ) );
        aStdFont.SetCharSet( osl_getThreadTextEncoding() );
        aStdFont.SetWeight( WEIGHT_NORMAL );
        aStdFont.SetFamilyName( u"Liberation Sans"_ustr );
        aStyleSettings.BatchSetFonts( aStdFont, aStdFont );

        aStdFont.SetFontSize(Size(0, 12));
        aStyleSettings.SetMenuFont(aStdFont);

        SvpSalGraphics* pGraphics = m_aGraphics.empty() ? nullptr : m_aGraphics.back();
        bool bFreeGraphics = false;
        if (!pGraphics)
        {
            pGraphics = dynamic_cast<SvpSalGraphics*>(AcquireGraphics());
            if (!pGraphics)
            {
                SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
                return;
            }
            bFreeGraphics = true;
        }
        rSettings.SetStyleSettings(aStyleSettings);
#ifndef IOS // For now...
        pGraphics->UpdateSettings(rSettings);
#endif
        if (bFreeGraphics)
            ReleaseGraphics(pGraphics);
    }
    else
        rSettings.SetStyleSettings(aStyleSettings);
}

void SvpSalFrame::Beep()
{
}

const SystemEnvData& SvpSalFrame::GetSystemData() const
{
    return m_aSystemChildData;
}

SalFrame::SalPointerState SvpSalFrame::GetPointerState()
{
    SalPointerState aState;
    aState.mnState = 0;
    return aState;
}

KeyIndicatorState SvpSalFrame::GetIndicatorState()
{
    return KeyIndicatorState::NONE;
}

void SvpSalFrame::SimulateKeyPress( sal_uInt16 /*nKeyCode*/ )
{
}

void SvpSalFrame::SetParent( SalFrame* pNewParent )
{
    if( m_pParent )
        std::erase(m_pParent->m_aChildren, this);
    m_pParent = static_cast<SvpSalFrame*>(pNewParent);
}

void SvpSalFrame::SetPluginParent( SystemParentData* )
{
}

void SvpSalFrame::ResetClipRegion()
{
}

void SvpSalFrame::BeginSetClipRegion( sal_uInt32 )
{
}

void SvpSalFrame::UnionClipRegion( tools::Long, tools::Long, tools::Long, tools::Long )
{
}

void SvpSalFrame::EndSetClipRegion()
{
}

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