/*--------------------------------------------------------------------------
  Project:  Manual 2D Library - m2d
  File:     m2d_pageView.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: 1758 $
 *-------------------------------------------------------------------------*/

#include <nitro.h>
#include <nnsys/misc.h>
#include <ntmv/m2d/inlines.h>
#include <ntmv/m2d/debug.h>
#include <ntmv/m2d/uiResDefines.h>
#include <ntmv/m2d/arcResourceAccessor.h>
#include <ntmv/m2d/manualPageResource.h>
#include <ntmv/m2d/page.h>
#include <ntmv/m2d/pane.h>
#include <ntmv/m2d/drawInfo.h>
#include <ntmv/m2d/objectConstructor.h>
#include <ntmv/m2d/pageView.h>
#include <ntmv/m2d/texMap.h>
#include <ntmv/m2d/common.h>

const int       ScrollDots = 4;         // Distance to move when the scroll button is pressed
const int       ScrollMaxMoveDots = 64; // Maximum scrolling distance
const int       ScrollSEDots = 32;      // Movement distance for scrolling sound effects

const int       WALLPAPERTEXTURE_HEIGHT = 16;   // Height of the wallpaper texture

namespace ntmv
{
namespace m2d
{

namespace
{

/*---------------------------------------------------------------------------*
  Name:         SetupCamera

  Description:  Sets the model view matrix and projection matrix.

  Arguments:    page:  Page object.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
SetupCamera(
    DrawInfo*   pDrawInfo,
    const Page& page
)
{
    const Rect rect = page.GetPageRect();

    // Set the layout Rect.
    pDrawInfo->SetPageRect(rect);
}

}   // namespace

/*---------------------------------------------------------------------------*
  Name:         PageView::PageView()

  Description:  Constructor.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
PageView::PageView()
:   mpAllocator(0),
    mpPage(0),
    mFonts(0),
    mFrameBuf(0),
    mViewPosY(0),
    mEndViewPosY(0),
    mPreViewPosY(0),
    mPreEndViewPosY(0),
    mNextViewPosY(0),
    mScrollSEPosY(0),
    mbScrollStart(false)
{}

/*---------------------------------------------------------------------------*
  Name:         PageView::~PageView()

  Description:  Destructor

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
PageView::~PageView()
{
    NTMV_LOG("PageView::~PageView()\n");
}

/*---------------------------------------------------------------------------*
  Name:         PageView::Init

  Description:  Performs initialization.

  Arguments:    pAllocator: Pointer to an NNS allocator.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
PageView::Init(NNSFndAllocator* pAllocator)
{
    mpAllocator = pAllocator;
}

/*---------------------------------------------------------------------------*
  Name:         SetPage

  Description:  Sets the Page.
                If 0 is specified, the Page that has been set will be deleted.

  Arguments:    pPage:  Pointer to a Page object.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
PageView::SetPage(Page* pPage)
{
    mpPage = pPage;
    if (mpPage)
    {
        ResetPosition();

        Calc();
    }
}

void
PageView::ResetPosition()
{
    mPreViewPosY = 0;
    InvalidateRect();

    mViewPosY = mPreViewPosY;
    mEndViewPosY = static_cast<s16>(mViewPosY + mViewSize.height);
    mNextViewPosY = mViewPosY;
}

bool
PageView::CheckScroll(
    bool    bUp,
    bool    bDown
)
{
    if (mpPage && mPreViewPosY < mPreEndViewPosY)
    {
        const int layoutHeight = GetScrollHeight();
        if (layoutHeight > mViewSize.height)
        {
            if (bUp)
            {
                mNextViewPosY = static_cast<s16>(Max(0, mViewPosY - ScrollDots));
            }
            else if (bDown)
            {
                mNextViewPosY = static_cast<s16>(Min(layoutHeight, mEndViewPosY + ScrollDots) - mViewSize.height);
            }
        }
    }

    return mViewPosY != mNextViewPosY;
}

bool
PageView::Scroll(s16 moveY)
{
    bool bUpdate = false;
    if (mpPage && mPreViewPosY < mPreEndViewPosY)
    {
        const int layoutHeight = GetScrollHeight();
        if (layoutHeight > mViewSize.height)
        {
            if (moveY < 0)
            {
                s16 addMoveY = Max(moveY, static_cast<s16>(- ScrollMaxMoveDots));
                mViewPosY = static_cast<s16>(Max(0, mPreViewPosY + addMoveY));
                if (mViewPosY != mPreViewPosY)
                {
                    mEndViewPosY = static_cast<s16>(mViewPosY + mViewSize.height);
                    bUpdate = true;
                }
            }
            if (moveY > 0)
            {
                s16 addMoveY = Min(moveY, static_cast<s16>(ScrollMaxMoveDots));
                mEndViewPosY = static_cast<s16>(Min(layoutHeight, mPreEndViewPosY + addMoveY));
                if (mEndViewPosY != mPreEndViewPosY)
                {
                    mViewPosY = static_cast<s16>(mEndViewPosY - mViewSize.height);
                    bUpdate = true;
                }
            }
        }
    }

    return bUpdate;
}

bool
PageView::DoScroll(bool* pScrollEvent)
{
    bool bScroll = false;
    if (mNextViewPosY != mViewPosY)
    {
        int distY = 0;
        bScroll = Scroll(static_cast<s16>(mNextViewPosY - mViewPosY));
        if (bScroll)
        {
            if (mbScrollStart)
            {
                mbScrollStart = false;
                mScrollSEPosY = mViewPosY;
                *pScrollEvent = true;
            }
            else if (Abs((distY = mViewPosY - mScrollSEPosY)) >= ScrollSEDots)
            {
                mScrollSEPosY += distY > 0 ? ScrollSEDots: -ScrollSEDots;
                *pScrollEvent = true;
            }
        }
    }

    return bScroll;
}

void
PageView::SetupDrawInfo(DrawInfo* pDrawInfo)
{
    SetupCamera(pDrawInfo, *mpPage);

    pDrawInfo->SetFrameBuf(mFrameBuf);
    pDrawInfo->SetFrameBufSize(mFrameBufSize);
    pDrawInfo->SetFont(mFonts);
    pDrawInfo->SetPage(mpPage);
}

void
PageView::Calc()
{
    if (! mpPage)
    {
        return;
    }

    DrawInfo drawInfo;
    SetupDrawInfo(&drawInfo);
    mpPage->Calc(drawInfo);
}

void
PageView::Draw()
{
    if (! mpPage)
    {
        return;
    }

    DrawInfo drawInfo;
    SetupDrawInfo(&drawInfo);

    Rect rect;
    if (mPreViewPosY == mViewPosY)
    {
        if (mPreEndViewPosY < mEndViewPosY)  // When there are no valid lines displayed
        {
            rect = Rect(0, mPreEndViewPosY, mViewSize.width, mEndViewPosY);
        }
    }
    else
    {
        if (mPreViewPosY < mViewPosY)
        {
            rect = Rect(0, mPreEndViewPosY, mViewSize.width, mEndViewPosY);
        }
        else
        {
            rect = Rect(0, mViewPosY, mViewSize.width, mPreViewPosY);
        }
    }
    drawInfo.SetInvalidateRect(rect);
    mpPage->Draw(drawInfo);

    DrawPageBottom(drawInfo);
}

void
PageView::InvalidateRect()
{
    mPreEndViewPosY = mPreViewPosY;
}

const Rect
PageView::GetScreenRect(const Rect& layoutRect)
{
    Rect ret = layoutRect;
    ret.Move(0, static_cast<s16>(-mViewPosY));
    return ret;
}

/*---------------------------------------------------------------------------*
  Name:         PageView::GetScrollHeight

  Description:  Gets the height of the scrolling range.

  Arguments:    None.

  Returns:      Returns the height of the scrolling range.
 *---------------------------------------------------------------------------*/
s16
PageView::GetScrollHeight() const
{
    s16 height = 0;
    if (mpPage)
    {
        height = static_cast<s16>(Max(mpPage->GetPageRect().GetHeight(), detail::PAGEHEIGHT_MIN) + mViewSize.height - detail::PAGEHEIGHT_MIN);
    }

    return height;
}

/*---------------------------------------------------------------------------*
  Name:         PageView::IsScrollable

  Description:  Determines if scrolling is possible.

  Arguments:    None.

  Returns:      Returns TRUE when scrolling is possible.
 *---------------------------------------------------------------------------*/
bool
PageView::IsScrollable() const
{
    return GetScrollHeight() > mViewSize.height;
}

/*---------------------------------------------------------------------------*
  Name:         PageView::SetPageBottomImage

  Description:  Sets the image to draw at the bottom of the page.

  Arguments:    wallPaperImg:  Wallpaper image.

  Returns:      Returns TRUE when scrolling is possible.
 *---------------------------------------------------------------------------*/
void
PageView::SetPageBottomImage(
    const ImageHeader*  wallPaperImg
)
{
    mWallPaperTex.Set(wallPaperImg);

    NNS_ASSERT(WALLPAPERTEXTURE_HEIGHT == mWallPaperTex.GetSize().height);
}

/*---------------------------------------------------------------------------*
  Name:         PageView::DrawPageBottom

  Description:  Draws the bottom of the page.

  Arguments:    drawInfo:  DrawInfo object.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
PageView::DrawPageBottom(const DrawInfo& drawInfo)
{
    NNS_NULL_ASSERT(mpPage);

    const Rect& rect = drawInfo.GetInvalidateRect();
    const s16 pageHeight = mpPage->GetPageRect().GetHeight();

    // Position (y-coordinate) to start drawing the wallpaper
    const s16 wallStartY = static_cast<s16>(Max(pageHeight, detail::PAGEHEIGHT_MIN));

    // Determine if wallpaper should be drawn below the lower band
    if (rect.bottom >= wallStartY)
    {
        TexMap texMap;
        texMap.Set(&mWallPaperTex);
        const Size texSize = mWallPaperTex.GetSize();

        // Calculate the position to start rendering
        s16 y;
        if (rect.top <= wallStartY)
        {
            y = wallStartY;
        }
        else
        {
            // Calculate the position to start repeating vertically
            y = static_cast<s16>(rect.top - wallStartY);
            y &= ~(WALLPAPERTEXTURE_HEIGHT - 1);
            y += wallStartY;
        }

        // Draw the wallpaper
        for (; y < rect.bottom; y += texSize.height)
        for (s16 x = 0; x < mViewSize.width; x += texSize.width)
        {
            detail::DrawImage(drawInfo, Point(x, y), texMap);
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         PageView::GetPageHeaderText

  Description:  Gets the page header text.

  Arguments:    None.

  Returns:      Returns the page header text.
 *---------------------------------------------------------------------------*/
const wchar_t*
PageView::GetPageHeaderText() const
{
    return mpPage->GetPageHeader();
}

}   // namespace m2d
}   // namespace ntmv
