/* -*- 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 "accfrmobj.hxx"

#include <accmap.hxx>
#include "acccontext.hxx"

#include <viewsh.hxx>
#include <rootfrm.hxx>
#include <flyfrm.hxx>
#include <pagefrm.hxx>
#include <cellfrm.hxx>
#include <swtable.hxx>
#include <dflyobj.hxx>
#include <frmfmt.hxx>
#include <fmtanchr.hxx>
#include <dcontact.hxx>

#include <vcl/window.hxx>

namespace sw::access {

SwAccessibleChild::SwAccessibleChild()
    : mpFrame( nullptr )
    , mpDrawObj( nullptr )
    , mpWindow( nullptr )
{}

SwAccessibleChild::SwAccessibleChild( const SdrObject* pDrawObj )
    : mpFrame( nullptr )
    , mpDrawObj( nullptr )
    , mpWindow( nullptr )
{
    Init( pDrawObj );
}

SwAccessibleChild::SwAccessibleChild( const SwFrame* pFrame )
    : mpFrame( nullptr )
    , mpDrawObj( nullptr )
    , mpWindow( nullptr )
{
    Init( pFrame );
}

SwAccessibleChild::SwAccessibleChild( vcl::Window* pWindow )
    : mpFrame( nullptr )
    , mpDrawObj( nullptr )
    , mpWindow( nullptr )
{
    Init( pWindow );
}

SwAccessibleChild::SwAccessibleChild( const SwFrame* pFrame,
                                      const SdrObject* pDrawObj,
                                      vcl::Window* pWindow )
    : mpFrame( nullptr )
    , mpDrawObj( nullptr )
    , mpWindow( nullptr )
{
    if ( pFrame )
    {
        Init( pFrame );
    }
    else if ( pDrawObj )
    {
        Init( pDrawObj );
    }
    else if ( pWindow )
    {
        Init( pWindow );
    }
    OSL_ENSURE( (!pFrame || pFrame == mpFrame) &&
            (!pDrawObj || pDrawObj == mpDrawObj) &&
            (!pWindow || pWindow == mpWindow),
            "invalid frame/object/window combination" );

}

SwAccessibleChild::~SwAccessibleChild() = default;

void SwAccessibleChild::Init( const SdrObject* pDrawObj )
{
    mpDrawObj = pDrawObj;
    const SwVirtFlyDrawObj* pFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>(mpDrawObj);
    mpFrame = pFlyDrawObj ? pFlyDrawObj->GetFlyFrame() : nullptr;
    mpWindow = nullptr;
}

void SwAccessibleChild::Init( const SwFrame* pFrame )
{
    mpFrame = pFrame;
    mpDrawObj = mpFrame && mpFrame->IsFlyFrame()
                ? static_cast < const SwFlyFrame * >( mpFrame )->GetVirtDrawObj()
                : nullptr;
    mpWindow = nullptr;
}

void SwAccessibleChild::Init( vcl::Window* pWindow )
{
    mpWindow = pWindow;
    mpFrame = nullptr;
    mpDrawObj = nullptr;
}

bool SwAccessibleChild::IsFrameAccessible(const SwFrame& rFrame, bool bPagePreview)
{
    if (!rFrame.IsAccessibleFrame())
        return false;

    if (rFrame.IsCellFrame()
        && static_cast<const SwCellFrame&>(rFrame).GetTabBox()->GetSttNd() == nullptr)
        return false;

    if (rFrame.IsInCoveredCell())
        return false;

    return bPagePreview || !rFrame.IsPageFrame();
}

bool SwAccessibleChild::IsAccessible( bool bPagePreview ) const
{
    if ( mpFrame )
        return IsFrameAccessible(*mpFrame, bPagePreview);

    return mpDrawObj || mpWindow;
}

bool SwAccessibleChild::IsBoundAsChar() const
{
    bool bRet( false );

    if ( mpFrame )
    {
        bRet = mpFrame->IsFlyFrame() &&
               static_cast< const SwFlyFrame *>(mpFrame)->IsFlyInContentFrame();
    }
    else if ( mpDrawObj )
    {
        const SwFrameFormat* pFrameFormat = ::FindFrameFormat( mpDrawObj );
        bRet = pFrameFormat
               && (RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId());
    }
    else if ( mpWindow )
    {
        bRet = false;
    }

    return bRet;
}

SwAccessibleChild& SwAccessibleChild::operator=( const SdrObject* pDrawObj )
{
    Init( pDrawObj );
    return *this;
}

SwAccessibleChild& SwAccessibleChild::operator=( const SwFrame* pFrame )
{
    Init( pFrame );
    return *this;
}

SwAccessibleChild& SwAccessibleChild::operator=( vcl::Window* pWindow )
{
    Init( pWindow );
    return *this;
}

bool SwAccessibleChild::IsValid() const
{
    return mpFrame != nullptr ||
           mpDrawObj != nullptr ||
           mpWindow != nullptr;
}

bool SwAccessibleChild::IsVisibleChildrenOnly() const
{
    bool bRet( false );

    if ( !mpFrame )
    {
        bRet = true;
    }
    else
    {
        bRet = mpFrame->IsRootFrame() ||
               !( mpFrame->IsTabFrame() ||
                  mpFrame->IsInTab() ||
                  ( IsBoundAsChar() &&
                    static_cast<const SwFlyFrame*>(mpFrame)->GetAnchorFrame()->IsInTab() ) );
    }

    return bRet;
}

SwRect SwAccessibleChild::GetBox( const SwAccessibleMap& rAccMap ) const
{
    SwRect aBox;

    if ( mpFrame )
    {
        if ( mpFrame->IsPageFrame() &&
             static_cast< const SwPageFrame * >( mpFrame )->IsEmptyPage() )
        {
            aBox = SwRect( mpFrame->getFrameArea().Left(), mpFrame->getFrameArea().Top()-1, 1, 1 );
        }
        else if ( mpFrame->IsTabFrame() )
        {
            aBox = mpFrame->getFrameArea();
            aBox.Intersection( mpFrame->GetUpper()->getFrameArea() );
        }
        else
        {
            aBox = mpFrame->getFrameArea();
        }
    }
    else if( mpDrawObj )
    {
        const SwContact* const pContact = ::GetUserCall(mpDrawObj);
        // assume that a) the SwVirt* objects that don't have this are handled
        // by the mpFrame case above b) for genuine SdrObject this must be set
        // if it's connected to layout
        assert(dynamic_cast<SwDrawContact const*>(pContact));
        if (pContact)
        {
            SwPageFrame const*const pPage(const_cast<SwAnchoredObject *>(
                pContact->GetAnchoredObj(mpDrawObj))->FindPageFrameOfAnchor());
            if (pPage) // may end up here with partial layout -> not visible
            {
                aBox = SwRect( mpDrawObj->GetCurrentBoundRect() );
                // tdf#91260 drawing object may be partially off-page
                aBox.Intersection(pPage->getFrameArea());
            }
        }
    }
    else if ( mpWindow )
    {
        vcl::Window* pWin = rAccMap.GetShell().GetWin();
        if (pWin)
        {
            aBox = SwRect( pWin->PixelToLogic(
                                            tools::Rectangle( mpWindow->GetPosPixel(),
                                                       mpWindow->GetSizePixel() ) ) );
        }
    }

    return aBox;
}

SwRect SwAccessibleChild::GetBounds( const SwAccessibleMap& rAccMap ) const
{
    SwRect aBound;

    if( mpFrame )
    {
        if( mpFrame->IsPageFrame() &&
            static_cast< const SwPageFrame * >( mpFrame )->IsEmptyPage() )
        {
            aBound = SwRect( mpFrame->getFrameArea().Left(), mpFrame->getFrameArea().Top()-1, 0, 0 );
        }
        else
            aBound = mpFrame->GetPaintArea();
    }
    else if( mpDrawObj )
    {
        aBound = GetBox( rAccMap );
    }
    else if ( mpWindow )
    {
        aBound = GetBox( rAccMap );
    }

    return aBound;
}

bool SwAccessibleChild::AlwaysIncludeAsChild() const
{
    bool bAlwaysIncludedAsChild( false );

    if ( mpWindow )
    {
        bAlwaysIncludedAsChild = true;
    }

    return bAlwaysIncludedAsChild;
}

const SwFrame* SwAccessibleChild::GetParent( const bool bInPagePreview ) const
{
    const SwFrame* pParent( nullptr );

    if ( mpFrame )
    {
        if( mpFrame->IsFlyFrame() )
        {
            const SwFlyFrame* pFly = static_cast< const SwFlyFrame *>( mpFrame );
            if( pFly->IsFlyInContentFrame() )
            {
                // For RndStdIds::FLY_AS_CHAR the parent is the anchor
                pParent = pFly->GetAnchorFrame();
                OSL_ENSURE( SwAccessibleChild( pParent ).IsAccessible( bInPagePreview ),
                        "parent is not accessible" );
            }
            else
            {
                // In any other case the parent is the root frm
                // (in page preview, the page frame)
                if( bInPagePreview )
                    pParent = pFly->FindPageFrame();
                else
                    pParent = pFly->getRootFrame();
            }
        }
        else
        {
            SwAccessibleChild aUpper( mpFrame->GetUpper() );
            while( aUpper.GetSwFrame() && !aUpper.IsAccessible(bInPagePreview) )
            {
                aUpper = aUpper.GetSwFrame()->GetUpper();
            }
            pParent = aUpper.GetSwFrame();
        }
    }
    else if( mpDrawObj )
    {
        const SwDrawContact *pContact =
            static_cast< const SwDrawContact* >( GetUserCall( mpDrawObj ) );
        OSL_ENSURE( pContact, "sdr contact is missing" );
        if( pContact )
        {
            const SwFrameFormat *pFrameFormat = pContact->GetFormat();
            OSL_ENSURE( pFrameFormat, "frame format is missing" );
            if( pFrameFormat && RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId() )
            {
                // For RndStdIds::FLY_AS_CHAR the parent is the anchor
                pParent = pContact->GetAnchorFrame();
                OSL_ENSURE( SwAccessibleChild( pParent ).IsAccessible( bInPagePreview ),
                        "parent is not accessible" );

            }
            else
            {
                // In any other case the parent is the root frm
                SwFrame const*const pAnchor(pContact->GetAnchorFrame());
                if (pAnchor) // null if object removed from layout
                {
                    if (bInPagePreview)
                        pParent = pAnchor->FindPageFrame();
                    else
                        pParent = pAnchor->getRootFrame();
                }
            }
        }
    }
    else if ( mpWindow )
    {
        rtl::Reference<comphelper::OAccessible> pAcc = mpWindow->GetAccessible();
        if (pAcc.is())
        {
            css::uno::Reference<css::accessibility::XAccessible> xAccParent
                = pAcc->getAccessibleParent();
            if (xAccParent.is())
            {
                SwAccessibleContext* pAccParentImpl
                    = dynamic_cast<SwAccessibleContext*>(xAccParent.get());
                if (pAccParentImpl)
                {
                    pParent = pAccParentImpl->GetFrame();
                }
            }
        }
    }

    return pParent;
}

} // namespace sw::access

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