/*--------------------------------------------------------------------------
  Project:  Manual 2D Library - m2d
  File:     lyt_page.cpp

  Copyright (C)2007-2009 Nintendo  All Rights Reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law. They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Revision: $
 *-------------------------------------------------------------------------*/

#include <nitro.h>
#include <nnsys.h>
#include <ntmv/m2d/common.h>
#include <ntmv/m2d/objectConstructor.h>
#include <ntmv/m2d/types.h>
#include <ntmv/m2d/page.h>
#include <ntmv/m2d/pane.h>
#include <ntmv/m2d/picture.h>
#include <ntmv/m2d/textBox.h>
#include <ntmv/m2d/window.h>
#include <ntmv/m2d/drawInfo.h>
#include <ntmv/m2d/resourceAccessor.h>

namespace ntmv
{
namespace m2d
{

/*---------------------------------------------------------------------------*
  Name:         Page::Page

  Description:  Constructor

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
Page::Page()
:   mpRootPane(0),
    mPageSize(0, 0),
    mPageHeightMin(0)
{}

/*---------------------------------------------------------------------------*
  Name:         Page::Finalize

  Description:  Performs shutdown.

  Arguments:    objectConstructor:  A reference to an ObjectConstructor object.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
Page::Finalize(const ObjectConstructor& objectConstructor)
{
    // OS_Printf("Page::Finalize()\n");

    objectConstructor.DeleteObj(mpRootPane);

#if defined(NTMV_M2D_USE_DIRECTTEXTURE)
    objectConstructor.DeleteArray(mTextureList.texList, mTextureList.texNum);
#else   // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
    objectConstructor.DeletePrimArray(mTextureList.texList);
#endif  // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
}

/*---------------------------------------------------------------------------*
  Name:         Page::Init

  Description:  Performs initialization.

  Arguments:    objectConstructor:  A reference to an ObjectConstructor object.
                lytResBuf:          Pointer to a page resource.
                pResAcsr:           A pointer to the resource accessor.

  Returns:      Returns TRUE if initialization succeeds, or FALSE if it fails.
 *---------------------------------------------------------------------------*/
bool
Page::Init(
    const ObjectConstructor&    objectConstructor,
    const void*                 lytResBuf,
    ResourceAccessor*           pResAcsr
)
{
    NNS_NULL_ASSERT(lytResBuf);
    NNS_NULL_ASSERT(pResAcsr);

    const res::BinaryFileHeader *const pFileHead = static_cast<const res::BinaryFileHeader*>(lytResBuf);

    if (! detail::TestFileHeader(*pFileHead, res::FILESIGNATURE_NTPG))
    {
        NTMV_WARNING(false, "not ntpg file.");
        return false;
    }

    if (! detail::TestFileVersion(*pFileHead))
    {
        return false;
    }

    ResBlockSet resBlockSet = { pResAcsr, 0, 0, 0 };
    Pane* pParentPane = 0;
    u16 childPaneNum = 0;
    Pane* pLastPane = 0;

    const res::LinkElementList* resPageLinkInfoList = 0;
    u32 pageLinkIdx = 0;

    const res::LinkElementList* resPageAnchorInfoList = 0;
    u32 pageAnchorIdx = 0;

    const void* dataPtr = static_cast<const u8*>(lytResBuf) + pFileHead->headerSize;
    for (int i = 0; i < pFileHead->dataBlocks; ++i)
    {
        const res::DataBlockHeader* pDataBlockHead = static_cast<const res::DataBlockHeader*>(dataPtr);
        switch (detail::GetSignatureInt(pDataBlockHead->kind))
        {
        case res::DATABLOCKKIND_NAMEPOOL:
            resBlockSet.pNameBlock = static_cast<const res::NameBlock*>(dataPtr);
            break;

        case res::DATABLOCKKIND_TEXTPOOL:
            resBlockSet.pTextBlock = static_cast<const res::TextBlock*>(dataPtr);
            break;

        case res::DATABLOCKKIND_TEXTURELIST:
            {
                const res::TextureList *const pTextureList = static_cast<const res::TextureList*>(dataPtr);
                mTextureList.texNum = pTextureList->texNum;
#if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                mTextureList.texList = objectConstructor.NewArray<DirectTexture>(pTextureList->texNum);
#else   // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                mTextureList.texList = objectConstructor.NewArray<Texture>(pTextureList->texNum);
#endif  // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                NNS_NULL_ASSERT(mTextureList.texList);

                const res::Texture *const textures = pTextureList->textures;

                for (int texIdx = 0; texIdx < mTextureList.texNum; ++texIdx)
                {
                    const char *const fileName = detail::GetNamePoolStr(resBlockSet.pNameBlock ->offsets, textures[texIdx].nameStrIdx);
                    const ImageHeader *const pImg = static_cast<const ImageHeader*>(pResAcsr->GetResource(res::RESOURCETYPE_TEXTURE, fileName));
                    NNS_ASSERTMSG(pImg != NULL, "file not found. - %s", fileName);
#if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                    mTextureList.texList[texIdx].Set(objectConstructor, pImg);
#else   // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                    mTextureList.texList[texIdx].Set(pImg);
#endif  // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                }

                resBlockSet.pTextureList = &mTextureList;
            }
            break;

        case res::DATABLOCKKIND_PAGE:
            {
                const res::Page* pResLyt = static_cast<const res::Page*>(dataPtr);
                mPageSize = pResLyt->size;
                mBackColor = pResLyt->backColor;
                mPageHeader = detail::GetTextPoolStr(resBlockSet.pTextBlock ->offsets, pResLyt->titleTextIdx);
            }
            break;

        case res::DATABLOCKKIND_PANE:
        case res::DATABLOCKKIND_PICTURE:
        case res::DATABLOCKKIND_TEXTBOX:
        case res::DATABLOCKKIND_WINDOW:
            {
                Pane *const pPane = BuildPaneObj(objectConstructor, detail::GetSignatureInt(pDataBlockHead->kind), dataPtr, resBlockSet);
                NNS_NULL_ASSERT(pPane);

                // Set as the root pane when it has not been set yet
                if (mpRootPane == 0)
                {
                    mpRootPane = pPane;
                }

                // Register to the parent when the parent exists
                if (pParentPane)
                {
                    pParentPane->AppendChild(pPane);
                }

                pLastPane = pPane;
            }
            break;

        case res::DATABLOCKKIND_PANEBEGIN:
            NNS_NULL_ASSERT(pLastPane);
            pParentPane = pLastPane;    // Make the last created pane the parent
            childPaneNum = reinterpret_cast<const res::PaneBegin*>(pDataBlockHead)->paneNum;
            pParentPane->AllocChild(objectConstructor, childPaneNum);
            break;

        case res::DATABLOCKKIND_PANEEND:
            pLastPane = pParentPane;    // The parent pane is the last created pane
            pParentPane = pLastPane->GetParent();
            childPaneNum = 0;
            break;
        default:
            break;
        }

        dataPtr = static_cast<const u8*>(dataPtr) + pDataBlockHead->size;    // Move to the next block position
    }

    return true;
}

void
Page::Calc(const DrawInfo& drawInfo)
{
    if (! mpRootPane)
    {
        return;
    }

    mpRootPane->Calc(drawInfo, Point(0, 0));
}

void
Page::Draw(const DrawInfo& drawInfo)
{
    ClearBuffer(drawInfo);

    if (! mpRootPane)
    {
        return;
    }

    mpRootPane->Draw(drawInfo);
}

void
Page::ClearBuffer(const DrawInfo& drawInfo)
{
    // Clear the frame buffer
    const u16 backColor = static_cast<u16>(0x8000 | mBackColor);
    const Rect& invRect = drawInfo.GetInvalidateRect();
    const s16 yMax = Min(invRect.bottom, Max(mPageSize.height, mPageHeightMin));
    for (s16 y = invRect.top; y < yMax; ++y)
    {
        u16 *const lineBuf = detail::GetLineBuf(drawInfo, y);

        for (s16 x = invRect.left; x < invRect.right; ++x)
        {
            lineBuf[x] = backColor;
        }
    }
}

const Rect
Page::GetPageRect() const
{
    return Rect(0, 0, mPageSize.width, mPageSize.height);
}

Pane*
Page::BuildPaneObj(
    const ObjectConstructor&    objectConstructor,
    s32                         kind,
    const void*                 dataPtr,
    const ResBlockSet&          resBlockSet
)
{
    switch (kind)
    {
    case res::DATABLOCKKIND_PANE:
        {
            const res::Pane* pResPane = static_cast<const res::Pane*>(dataPtr);
            return objectConstructor.NewObj<Pane>(pResPane, resBlockSet);
        }
        break;
    case res::DATABLOCKKIND_PICTURE:
        {
            const res::Picture* pResPic = static_cast<const res::Picture*>(dataPtr);
            return objectConstructor.NewObj<Picture>(pResPic, resBlockSet);
        }
        break;
    case res::DATABLOCKKIND_TEXTBOX:
        {
            const res::TextBox* pBlock = static_cast<const res::TextBox*>(dataPtr);
            return objectConstructor.NewObj<TextBox>(pBlock, resBlockSet);
        }
        break;
    case res::DATABLOCKKIND_WINDOW:
        {
            const res::Window* pBlock = static_cast<const res::Window*>(dataPtr);
            return objectConstructor.NewObj<Window>(objectConstructor, pBlock, resBlockSet);
        }
        break;
    default:
        NNS_ASSERTMSG(false, "unknown data type");
    }

    return 0;
}

}   // namespace m2d
}   // namespace ntmv
