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

#include <nitro.h>
#include <nnsys/misc.h>
#include <nnsys/g2d.h>
#include <nnsys/mcs.h>
#include <ntmv/m2d/debug.h>
#include <ntmv/m2d/inlines.h>
#include <ntmv/m2d/graphicDefines.h>
#include <ntmv/m2d/uiResDefines.h>
#include <ntmv/m2d/Rect.h>
#include <ntmv/m2d/arcResourceAccessor.h>
#include <ntmv/m2d/manualTableOfContents.h>
#include <ntmv/m2d/manualTocPanel.h>
#include <ntmv/m2d/scrollButton.h>
#include <ntmv/m2d/scrollIndicator.h>
#include <ntmv/m2d/objectConstructor.h>
#include <ntmv/m2d/page.h>
#include <ntmv/m2d/picture.h>
#include <ntmv/m2d/textBox.h>
#include <ntmv/m2d/drawInfo.h>
#include <ntmv/m2d/manualPageResource.h>
#include <ntmv/m2d/manualTocResource.h>
#include <ntmv/m2d/langInfoResource.h>
#include <ntmv/m2d/langInfo.h>
#include <ntmv/m2d/touchPanel.h>
#include <ntmv/m2d/touchPanelData.h>
#include <ntmv/m2d/pageHeaderPanel.h>
#include <ntmv/m2d/manualView.h>
#include <ntmv/m2d/sound_data.sadl>
#include <ntmv/m2d/objG2dInfo.h>
#include <ntmv/m2d/nlv_skin_NANR_LBLDEFS.h>
#include <ntmv/m2d/nlv_skin_up_obj_NANR_LBLDEFS.h>
#include <cstring>
#include <extras_string.h>

// Path to the root directory for the archive of the UI resource archive
#define NTMV_M2D_UIRESARCDIR    NTMV_M2D_UIROOTDIR "/arc"

// Path to the root directory of the manual's Help page
#define NTMV_M2D_HELPPAGEROOTDIR    NTMV_M2D_MANARCID ":/gpArc"

namespace ntmv
{
namespace m2d
{

namespace
{

const int       FADER_BRIGHTNESS_MAX    = 16;   // Maximum fader brightness

const int       LAUNCH_FADER_FRAME_COUNT    = 17;   // Frame count for the startup and shutdown fader

const int       PAGE_FADER_FRAME_COUNT      = 13;   // Frame count for the page-switch fader

const int       PAGE_FADER_MOVE_START_FRAME =  6;   // Horizontal starting frame for the page-switch fader

#if ! defined(DOC_SCROLL_DEBUG)
const s16       DOCSCROLL_MAX   = 10;
const s16       DOCSCROLL_LOSS  =  7;
#endif

void
DEMOStartDisplay(void)
{
    OS_WaitVBlankIntr();               // Waiting for the end of the V-Blank interrupt

}

/*---------------------------------------------------------------------------*
  Name:         SetMasterBrightness

  Description:  Sets the master brightness for the main screen and the sub-screen.

  Arguments:    brightness: Value for adjusting the brightness (between -16 and 16)

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
SetMasterBrightness(int brightness)
{
    GX_SetMasterBrightness(brightness);
    GXS_SetMasterBrightness(brightness);
}

/*---------------------------------------------------------------------------*
  Name:         SetAlphaBlendForTocUI

  Description:  Configures alpha blending.
                This is used by the Contents screen.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
SetAlphaBlendForTocUI()
{
    // Alpha-blend BG0 with other screens
    const int BlendPlane2 =
          GX_BLEND_PLANEMASK_BG1
        | GX_BLEND_PLANEMASK_BG2
        | GX_BLEND_PLANEMASK_BG3
        | GX_BLEND_PLANEMASK_OBJ
        | GX_BLEND_PLANEMASK_BD;
    G2_SetBlendAlpha(GX_BLEND_PLANEMASK_BG0, BlendPlane2, 6, 10);
}

/*---------------------------------------------------------------------------*
  Name:         SetBackDropColor

  Description:  Sets the backdrop color.

  Arguments:    rgb: Color to set

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
SetBackDropColor(GXRgb rgb)
{
    const u16 backDropCol = rgb;
    GX_LoadBGPltt(&backDropCol, 0, sizeof(backDropCol));
    GXS_LoadBGPltt(&backDropCol, 0, sizeof(backDropCol));
}

/* ------------------------------------------------------------------------
        Page Link Related
   ------------------------------------------------------------------------ */



/*---------------------------------------------------------------------------*
  Name:         LoadBG3

  Description:  Transfers page content to the VRAM assigned to BG3.

  Arguments:    pageView:     Page to transfer
                frmRgn:       Range of positions in the original frame buffer being transferred
                loadFunc:     Transfer function. Specifies LoadBG3 or GXS_LoadBG3Bmp.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
LoadBG3(
    const PageView& pageView,
    VRegion         frmRgn,
    void            (*loadFunc)(const void *pSrc, u32 offset, u32 szByte)
)
{
    const VRegion viewVRgn = pageView.GetViewVRegion();
    const VRegion preViewVRgn = pageView.GetPreViewVRegion();

    VRegion srcRgn;

    if (preViewVRgn.top <= viewVRgn.top)
    {
        srcRgn.Set(
            Max(static_cast<s16>(viewVRgn.top + frmRgn.top), Min(preViewVRgn.bottom, static_cast<s16>(preViewVRgn.top + frmRgn.bottom))),
            static_cast<s16>(viewVRgn.top + frmRgn.bottom));
    }
    else
    {
        srcRgn.Set(
            static_cast<s16>(viewVRgn.top + frmRgn.top),
            static_cast<s16>(preViewVRgn.top + frmRgn.top));
    }

    const Size frameBufSize = pageView.GetFrameBufSize();
    NNS_ASSERT(VRAM_WIDTH == frameBufSize.width);

    // The height should be a multiple of 256
    NNS_ASSERT(frameBufSize.height != 0 && (frameBufSize.height & (0x100 - 1)) == 0);

    const s32 vramHMask = VRAM_HEIGHT - 1;
    const s32 frmHMask = frameBufSize.height - 1;

    s32 frmY = srcRgn.top & frmHMask;
    s32 restHeight = srcRgn.GetHeight();
    while (restHeight > 0)
    {
        const s32 vramY = frmY & vramHMask;
        const s32 loadHeight = Min(restHeight, VRAM_HEIGHT - vramY);

        loadFunc(
            &pageView.GetFrameBuf()[frmY * pageView.GetFrameBufSize().width],
            vramY * VRAM_WIDTH * sizeof(u16),
            loadHeight * VRAM_WIDTH * sizeof(u16));

        restHeight -= loadHeight;
        frmY = (frmY + loadHeight) & frmHMask;
    }
}

void*
GetResource(const char* resPath)
{
    return NNS_FndGetArchiveFileByName(resPath);
}

/*---------------------------------------------------------------------------*
  Name:         CheckGXDMA

  Description:  Checks whether the DMA used by the GX library has been configured.

  Arguments:    None.

  Returns:      Returns TRUE if the DMA used by the GX library is currently configured to be used.
 *---------------------------------------------------------------------------*/
bool
CheckGXDMA()
{
    u32 dmaNo = GX_GetDefaultDMA();
    if (dmaNo <= MI_DMA_MAX_NUM)
    {
        return true;
    }

    if (OS_IsRunOnTwl())
    {
        #ifdef SDK_TWL
            dmaNo = GX_GetDefaultNDMA();
            if (dmaNo <= MI_NDMA_MAX_NUM)
            {
                return true;
            }
        #endif
    }

    return false;
}

/*---------------------------------------------------------------------------*
  Name:         GetPageViewBottom

  Description:  Gets the Y coordinate for the base of the PageView.

  Arguments:    pManualTocPanel: A pointer to a ManualTocPanel object.

  Returns:      Returns the Y coordinate for the base of the PageView.
 *---------------------------------------------------------------------------*/
int
GetPageViewBottom(const ManualTocPanel* pManualTocPanel)
{
    int bottom = detail::DOWNPAGEVIEW_HEIGHT;
    if (pManualTocPanel->IsOpened())
    {
        bottom = 0;
    }

    return bottom;
}

}   // Namespace

#if ! defined(DOC_SCROLL_DEBUG)
const s16   ManualView::mDocScrollMax   = DOCSCROLL_MAX;
const s16   ManualView::mDocScrollLoss  = DOCSCROLL_LOSS;
#endif

ManualView::ManualView()
:
    mManualArcData(0),
    mUiResArcData(0),
    mbMountManual(false),
    mbMountUIRes(false),
    mpObjManagerMain(0),
    mpObjManagerSub(0),
    mpManualToc(0),
    mpPageHeaderPanel(0),
    mpManualTocPanel(0),
    mpUITextPage(0),
    mpScrollButton(0),
    mpPage(0),
    mPageOrder(0),
    mTouchOnDispY(0),
    mPreDragDocY(0),
    mDocDragDir(0),
    mTouchOnDocY(0),
    mMaxMovePosY(0),
    mTouchMoveY(0),
    mInputKind(INPUTKIND_NONE),
    mbTouchDoc(false),
    mbExit(false),
    mbSetDMA(false),
    mbEnabledVBlankIntr(false),
    mbPMAutoExit(false)
#if defined(DOC_SCROLL_DEBUG)
    ,
    mDocScrollMax(DOCSCROLL_MAX),
    mDocScrollLoss(DOCSCROLL_LOSS)
#endif
{
    for (int i = 0; i < FONTSIZE_MAX; ++i)
    {
        mFonts[i] = 0;
    }

    for (int i = 0; i < DISPTYPE_MAX; ++i)
    {
        mpPanels[i] =0;
    }
 
    for (int i = 0; i < DISPTYPE_MAX; ++i)
    {
        mpScrollIndicators[i] = 0;
    }
}

ManualView::~ManualView()
{
    ClearNtpg();

    ObjectConstructor objectConstructor(&mAllocator);

    for (int i = 0; i < DISPTYPE_MAX; ++i)
    {
        objectConstructor.DeleteObj(mpPanels[i]);
    }
    objectConstructor.DeleteObj(mpManualToc);

    objectConstructor.DeleteObj(mpUITextPage);

    objectConstructor.DeleteObj(mpObjManagerSub);
    objectConstructor.DeleteObj(mpObjManagerMain);

    if (mUiResArcData)
    {
        NNS_FndFreeToAllocator(&mAllocator, mUiResArcData);
    }

    if (mManualArcData)
    {
        NNS_FndFreeToAllocator(&mAllocator, mManualArcData);
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::Init

  Description:  Performs initialization.

  Arguments:    pAllocator:     Pointer to the NITRO-System allocator
                manualFilePath: Path to the manual archive file
                uiResFilePath:  Path to the UI resource file
                fonts:          Array of font data pointers
                lang:           Language to use to display the manual.
                                The ISO 639 language code (two letters) stored in the upper and lower bytes using character codes.
                                

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::Init(
    const NNSFndAllocator*  pAllocator,
    const char*             manualFilePath,
    const char*             uiResFilePath,
    const NNSG2dFont *const fonts[],
    u16                     lang
)
{
    InitLib(pAllocator);

    mManualArcData = detail::LoadBLZFile(manualFilePath, &mAllocator);
    mUiResArcData = detail::LoadBLZFile(uiResFilePath, &mAllocator, detail::UI_RES_ALIGN);
    void *const uiRes = RoundUp(mUiResArcData, detail::UI_RES_ALIGN);

    InitCommon(mManualArcData, uiRes, fonts, lang);
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::Init

  Description:  Performs initialization.

  Arguments:    pAllocator:    Pointer to the NITRO-System allocator
                manualArcData: Manual archive data
                uiResource:    UI resource
                fonts:         Array of font data pointers
                lang:          Language to use to display the manual.
                               The ISO 639 language code (two letters) stored in the upper and lower bytes using character codes.
                               

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::Init(
    const NNSFndAllocator*  pAllocator,
    void*                   manualArcData,
    void *const             uiResource,
    const NNSG2dFont *const fonts[],
    u16                     lang
)
{
    NNS_ASSERT(uiResource == RoundUp(uiResource, detail::UI_RES_ALIGN));

    InitLib(pAllocator);
    InitCommon(manualArcData, uiResource, fonts, lang);
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::InitLib

  Description:  Initializes the SDK.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::InitLib(const NNSFndAllocator* pAllocator)
{
    mAllocator = *pAllocator;

    OS_Init();
    FX_Init();

    if (OS_IsRunOnTwl())
    {
        #ifdef SDK_TWL
            mbPMAutoExit = FALSE != PM_GetAutoExit();
            if (! mbPMAutoExit)
            {
                // Set to automatically quit
                PM_SetAutoExit(true);
            }
        #endif
    }

    mbEnabledVBlankIntr = 0 != GX_VBlankIntr(true); // Calling to get setting value.
                                                    // Cleared by GX_Init() below.

    GX_Init();
    // GX_SetPower(GX_POWER_ALL);   // This is done in GX_Init()

    if (! CheckGXDMA())
    {
        // Configure DMA if this has not been done
        GX_SetDefaultDMA(GX_DEFAULT_DMAID); // DMA number used by GX functions
        mbSetDMA = true;
    }
        
    GX_DispOff();
    GXS_DispOff();

    mGamePad.Read();                    // Dummy read to save previous key input

    mTouchPanel.InitHardware();

    (void)GX_VBlankIntr(true);

    InitVRAM();
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::InitCommon

  Description:  Initializes common settings.

  Arguments:    manualArcData: Manual archive data
                uiResource:    UI resource
                fonts:         Array of font data pointers
                lang:          Language to use to display the manual.
                               The ISO 639 language code (two letters) stored in the upper and lower bytes using character codes.
                               

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::InitCommon(
    void*                   manualArcData,
    void *const             uiResource,
    const NNSG2dFont *const fonts[],
    u16                     lang
)
{
    NNS_NULL_ASSERT(manualArcData);
    NNS_NULL_ASSERT(uiResource);
    NNS_NULL_ASSERT(fonts);

    InitDisplay();

    mbMountManual = 0 != NNS_FndMountArchive(&mManualArc, NTMV_M2D_MANARCID, manualArcData);
    NNS_ASSERTMSG(mbMountManual, "NNS_FndMountArchive(" NTMV_M2D_MANARCID ") failed.");

    // Specify the directory that will be used as the archive data and the resource root
    mManArcAccessor.Attach(NTMV_M2D_MANROOTDIR);

    // Specify the directory that will be used as the archive data and the resource root
    mUIResAccessor.Attach(NTMV_M2D_HELPPAGEROOTDIR);

    mbMountUIRes = 0 != NNS_FndMountArchive(&mUiResArc, NTMV_M2D_UIARCID, uiResource);
    NNS_ASSERTMSG(mbMountUIRes, "NNS_FndMountArchive(" NTMV_M2D_UIARCID ") failed.");

    InitFont(fonts);

    // Initialize sound data
    {
        void *const soundData = GetResource(NTMV_M2D_UIROOTDIR "/snd/sound_data.sdat");
        NNS_NULL_ASSERT(soundData);
        NNS_ASSERT(soundData == RoundUp(soundData, detail::UI_RES_ALIGN));
        mSndMgr.init(static_cast<u8*>(soundData));
    }

    ObjectConstructor objectConstructor(&mAllocator);

    mPageView.Init(&mAllocator);

    // Load and set the texture for rendering the bottom of the page
    const ImageHeader *const wallPaperImg = static_cast<ImageHeader*>(GetResource(NTMV_M2D_UIROOTDIR "/nttf/wallpaper"));

    mPageView.SetPageBottomImage(wallPaperImg);
    mPageView.SetFrameBuf(&mFrameBuf[0], Size(FRAMEBUF_WIDTH, FRAMEBUF_HEIGHT));
    mPageView.SetViewSize(Size(GX_LCD_SIZE_X, detail::WHOLEPAGEVIEW_HEIGHT));

    // Initialization related to the OAM manager
    NNS_G2dInitOamManagerModule();

    // Initialize the OBJ manager. This is called after VRAM configuration (InitDisplay).
    mpObjManagerMain = objectConstructor.NewObj<ObjManager>();
    NNS_NULL_ASSERT(mpObjManagerMain);
    mpObjManagerMain->InitializeMain(&mAllocator);
    mpObjManagerSub = objectConstructor.NewObj<ObjManager>();
    NNS_NULL_ASSERT(mpObjManagerSub);
    mpObjManagerSub->InitializeSub(&mAllocator);
    ObjManager::LoadCommonRes();

    mpPageHeaderPanel = objectConstructor.NewObj<PageHeaderPanel>();
    NNS_NULL_ASSERT(mpPageHeaderPanel);
    mpPageHeaderPanel->Init(mpObjManagerSub, mFonts);

    static const u16 scrollCellIdxs[ScrollIndicator::UI_MAX] =
    {
        NANR_nlv_skin_up_obj_CellAnime1,
        NANR_nlv_skin_up_obj_CellAnime2,
    };
    mpScrollIndicators[DISPTYPE_UP] = objectConstructor.NewObj<ScrollIndicator>();
    NNS_NULL_ASSERT(mpScrollIndicators[DISPTYPE_UP]);
    mpScrollIndicators[DISPTYPE_UP]->Init(
        objectConstructor,
        mpObjManagerSub,
        Point(
            GX_LCD_SIZE_X - (detail::SCROLLINDICATOR_WIDTH - detail::SCROLLINDICATOR_WIDTH / 2),
            detail::PAGEHEADERVIEW_HEIGHT + detail::UPPAGEVIEW_HEIGHT / 2
        ),
        detail::UPPAGEVIEW_HEIGHT,
        detail::UPPAGEVIEW_HEIGHT,
        scrollCellIdxs);

    UIElement* elements[] = { mpPageHeaderPanel, mpScrollIndicators[DISPTYPE_UP] };
    mpPanels[DISPTYPE_UP] = objectConstructor.NewObj<UIPanel>();
    NNS_NULL_ASSERT(mpPanels[DISPTYPE_UP]);
    mpPanels[DISPTYPE_UP]->Init(
        objectConstructor,
        elements,
        sizeof(elements) / sizeof(elements[0]),
        Point(0, 0),
        Size(GX_LCD_SIZE_X, GX_LCD_SIZE_Y));

    SetLanguage(lang);  // Language settings

    // Initialize the control panel on the Contents page
    {
        // Loading of the page source for the Control Panel text
        const void* pageRes = mUIResAccessor.GetLocalizeResource(res::RESOURCETYPE_PAGE, NTMV_M2D_CTRLPANELTEXTFILE);
        NNS_NULL_ASSERT(pageRes);

        const ObjectConstructor& objectConstructor(&mAllocator);

        mpUITextPage = objectConstructor.NewObj<Page>();
        NNS_NULL_ASSERT(mpUITextPage);

        mpUITextPage->Init(objectConstructor, pageRes, &mUIResAccessor);

        Pane *const pRootPane = mpUITextPage->GetRootPane();
        NNS_NULL_ASSERT(pRootPane);

        NNS_ASSERT(pRootPane->GetChildNum() == detail::CTRLPANELTEXT_MAX);

            // Set Control Panel text to the wchar_t pointer array
        const wchar_t* textAry[detail::CTRLPANELTEXT_MAX];
        Pane * *const paneAry = pRootPane->GetChildArray();
        for (int i = 0; i < detail::CTRLPANELTEXT_MAX; ++i)
        {
            textAry[i] = static_cast<TextBox*>(paneAry[i])->GetString();
        }

        mpManualTocPanel = objectConstructor.NewObj<ManualTocPanel>();
        NNS_NULL_ASSERT(mpManualTocPanel);

        mpManualTocPanel->Init(objectConstructor, mpObjManagerMain, mFonts, paneAry[detail::CTRLPANELTEXT_PREVPAGE]->GetSize().width, textAry, &mSndMgr);
    }

    mpScrollButton = mpManualTocPanel->GetScrollButton();

    static const u16 scrollCellIdxsDown[ScrollIndicator::UI_MAX] =
    {
        NANR_nlv_skin_scroll_arrow,
        NANR_nlv_skin_scroll_arrow,
    };
    mpScrollIndicators[DISPTYPE_DOWN] = objectConstructor.NewObj<ScrollIndicator>();
    NNS_NULL_ASSERT(mpScrollIndicators[DISPTYPE_DOWN]);
    mpScrollIndicators[DISPTYPE_DOWN]->Init(
        objectConstructor,
        mpObjManagerMain,
        Point(
            GX_LCD_SIZE_X - (detail::SCROLLINDICATOR_WIDTH - detail::SCROLLINDICATOR_WIDTH / 2),
            - detail::UPPAGEVIEW_HEIGHT + detail::WHOLEPAGEVIEW_HEIGHT / 2
        ),
        detail::WHOLEPAGEVIEW_HEIGHT,
        detail::UPPAGEVIEW_HEIGHT,
        scrollCellIdxsDown);

    UIElement* downElements[] = { mpManualTocPanel, mpScrollIndicators[DISPTYPE_DOWN] };
    mpPanels[DISPTYPE_DOWN] = objectConstructor.NewObj<UIPanel>();
    NNS_NULL_ASSERT(mpPanels[DISPTYPE_DOWN]);
    mpPanels[DISPTYPE_DOWN]->Init(
        objectConstructor,
        downElements,
        sizeof(downElements) / sizeof(downElements[0]),
        Point(0, 0),
        Size(GX_LCD_SIZE_X, GX_LCD_SIZE_Y));

    // Set the backdrop color to white to match the master brightness, which is set to MAX (white) initially.
    // 
    // Do this after the BG palette data has been loaded.
    SetBackDropColor(GX_RGB(31, 31, 31));

    LoadStartPages();

    DEMOStartDisplay();
}

void
ManualView::Run()
{
    // Decrease the brightness from pure white.
    // The display content is not drawn initially, so the brightness must be set to the maximum value to avoid any unintended display.
    // 
    // 
    {
        OS_WaitVBlankIntr();    // V-Blank wait

        // Turn on output to the LCD
        GX_DispOn();
        GXS_DispOn();

        for (int cnt = LAUNCH_FADER_FRAME_COUNT; cnt > 0;)
        {
            --cnt;
            // Set the initial frame to the maximum brightness
            const int bright = cnt * FADER_BRIGHTNESS_MAX / (LAUNCH_FADER_FRAME_COUNT - 1);

            Draw();

            OS_WaitVBlankIntr();    // V-Blank wait

            SetMasterBrightness(bright);

            TransferDrawData();
        }
    }

    //---------------------
    //  Main loop
    //---------------------
    while (! mbExit)
    {
        mGamePad.Read();

        // Enter sleep mode if the system is closed.
        if (PAD_DetectFold())
        {
            NTMV_LOG("Go sleep mode\n");

            // Awake from sleep mode only when the system is opened.
            // Accessing a Game Card or Game Pak is not expected, so insertion and removal of them are not set as a trigger.
            // 
            PM_GoSleepMode(PM_TRIGGER_COVER_OPEN, 0, 0);

            NTMV_LOG("Return sleep mode\n");
        }

        {
            m2d::TouchPanelData tpData;

            mTouchPanel.GetData(&tpData);

            if (tpData.bTouchOn)
            {
                NTMV_LOG("touch on [%d, %d]\n", tpData.point.x, tpData.point.y);
            }
            else
            {
                if (tpData.bTouchOffTrigger)
                {
                    NTMV_LOG("touch off trigger [%d, %d]\n", tpData.point.x, tpData.point.y);
                }
            }

            bool bHandled = false;
            switch (mInputKind)
            {
            case INPUTKIND_NONE:
                bHandled = 
                        OnTouchScrollUI(tpData)
                    ||  OnTouchManualToc(tpData)
                    ||  OnTouchDocument(tpData)
                    ||  OnKeyScroll()
                    ||  OnKeyEvent();
                break;
            case INPUTKIND_TPSCROLL:    OnTouchScrollUI(tpData);    break;
            case INPUTKIND_TPPOINTING:  OnTouchManualToc(tpData);   break;
            case INPUTKIND_DOC:         OnTouchDocument(tpData);    break;
            case INPUTKIND_KEYSCROLL:   OnKeyScroll();              break;
            }
        }

        Draw();

        OS_WaitVBlankIntr();    // V-Blank wait

        TransferDrawData();
    }

    // Change gradually to pure white
    for (int cnt = 0; cnt < LAUNCH_FADER_FRAME_COUNT; ++cnt)
    {
        const int bright = cnt * FADER_BRIGHTNESS_MAX / (LAUNCH_FADER_FRAME_COUNT - 1);

        Draw();

        OS_WaitVBlankIntr();    // V-Blank wait

        SetMasterBrightness(bright);

        TransferDrawData();
    }

    ResetHardware();
}

void
ManualView::Draw()
{
    mSndMgr.update();

    bool bScrollEvent = false;
    if (mPageView.DoScroll(&bScrollEvent))
    {
        mpScrollButton->Update(mPageView.GetViewPosY());
        for (int i = 0; i < DISPTYPE_MAX; ++i)
        {
            mpScrollIndicators[i]->UpdateViewY(mPageView.GetViewPosY());
        }
    }

    if (bScrollEvent)
    {
        mSndMgr.startSe(TWL_MAN_SE_SCROLL_TRG);
    }

    mPageView.Draw();

    // Flush the frame buffer
    DC_FlushRange(mFrameBuf, sizeof(mFrameBuf));

    ObjG2dInfo objG2dInfoMain(mpObjManagerMain);
    ObjG2dInfo objG2dInfoSub(mpObjManagerSub);

    mpScrollButton->Animate();
    mpScrollIndicators[DISPTYPE_UP]->Animate(mpObjManagerSub);
    mpScrollIndicators[DISPTYPE_DOWN]->Animate(mpObjManagerMain);
    mpManualTocPanel->Animate();

    mpPanels[DISPTYPE_UP]->Draw(objG2dInfoSub, Point(0, 0));
    mpPanels[DISPTYPE_DOWN]->Draw(objG2dInfoMain, Point(0, 0));
}

void
ManualView::TransferDrawData(s16 docPosX)
{
    // Transfer OBJ data
    mpObjManagerMain->TransferDataToHW();
    mpObjManagerSub->TransferDataToHW();
    mpManualTocPanel->TransferBGDataToHW();

    /* Configure the affine conversion that is applied to the BG3 surface */
    {
        /* Generate affine conversion matrix */
        MtxFx22 mtx;
        MTX_Identity22(&mtx);

        s32 posY = detail::UPPAGEVIEW_HEIGHT - GX_LCD_SIZE_Y + mPageView.GetViewPosY();

        G2S_SetBG3Affine(&mtx,                  // Conversion matrix
                        0,                      // Rotation center (x) coordinate
                        0,                      // Rotation center (y) coordinate
                        docPosX,                // Pre-rotation coordinate (x)
                        posY);                  // Pre-rotation coordinate (y)
        G2_SetBG3Affine(&mtx,                   // Conversion matrix
                        0,                      // Rotation center (x) coordinate
                        0,                      // Rotation center (y) coordinate
                        docPosX,                // Pre-rotation coordinate (x)
                        posY + GX_LCD_SIZE_Y);  // Pre-rotation coordinate (y)
    }

    if (docPosX == 0)    // When this is 0, disable windows
    {
        if (GX_WNDMASK_NONE != GXS_GetVisibleWnd())
        {
            if (! mpManualTocPanel->IsOpened())
            {
                GX_SetVisibleWnd(GX_WNDMASK_NONE);
            }
            GXS_SetVisibleWnd(GX_WNDMASK_NONE);
        }
    }
    else    // When this is non-zero, use a mask in the window to hide items that wrap beyond the area
    {
        if (GX_WNDMASK_NONE == GXS_GetVisibleWnd())
        {
            if (! mpManualTocPanel->IsOpened())
            {
                GX_SetVisibleWnd(GX_WNDMASK_W0);
            }
            GXS_SetVisibleWnd(GX_WNDMASK_W0);
        }

        SetDocFade(docPosX);
    }

    // Transfer bitmap data to BG3
    LoadBG3(
        mPageView,
        VRegion(0, detail::UPPAGEVIEW_HEIGHT),
        GXS_LoadBG3Bmp);
    LoadBG3(
        mPageView,
        VRegion(detail::UPPAGEVIEW_HEIGHT, detail::UPPAGEVIEW_HEIGHT + detail::DOWNPAGEVIEW_HEIGHT),
        GX_LoadBG3Bmp);

    // Draw() should be called at least once before this
    mPageView.PreserveOffset();
}

bool
ManualView::OnTouchManualToc(const TouchPanelData& tpData)
{
    ManualTocTouchResult result;
    bool bHandled = mpManualTocPanel->TestTouchPanel(&result, tpData);
    if (bHandled)
    {
        switch (result.eventType)
        {
        case MANUALTOCEVENT_EXIT:
            ProcessExitButton();
            break;

        case MANUALTOCEVENT_OPENCLOSE:
            SetUpDispScrollIndicatorState(result.state == MANUALTOCDISPSTATE_OPENED);
            break;

        case MANUALTOCEVENT_HELPPAGESELECTED:
            ProcessHelpButton(result.state == MANUALTOCHELPSTATE_SELECTED);
            break;

        case MANUALTOCEVENT_HEADERSELECTCHANGED:
        case MANUALTOCEVENT_CHAPTERSELECTCHANGED:
            LoadSelectedPage();
            break;

        }

        if (tpData.bTouchOnTrigger)
        {
            mInputKind = INPUTKIND_TPPOINTING;
        }
    }

    if (tpData.bTouchOffTrigger)
    {
        mInputKind = INPUTKIND_NONE;
        bHandled = true;
    }

    return bHandled;
}

bool
ManualView::OnTouchScrollUI(const TouchPanelData& tpData)
{
    if (! mpScrollButton)
    {
        return false;
    }

    bool bHandled = false;
    ScrollButtonResult result;

    if (mpScrollButton->TestTouchPanel(&result, tpData))
    {
        bHandled = true;

        switch (result.eventType)
        {
        case SCROLLBUTTON_EVENTTYPE_SCROLL:
            if (mInputKind != INPUTKIND_TPSCROLL)
            {
                mPageView.SetScrollStart(true);
                mInputKind = INPUTKIND_TPSCROLL;
            }
            mPageView.CheckScroll(result.bScrollUp, result.bScrollDown);
            break;
        case SCROLLBUTTON_EVENTTYPE_NONE:
            if (tpData.bTouchOnTrigger)
            {
                mSndMgr.startSe(TWL_MAN_SE_INVALID);
            }
            break;
        }

        if (tpData.bTouchOffTrigger)
        {
            mInputKind = INPUTKIND_NONE;
        }
    }

    return bHandled;
}

bool
ManualView::OnTouchDocument(const TouchPanelData& tpData)
{
    if (! mPageView.HasPage() || ! mPageView.IsScrollable())
    {
        return false;
    }

    if (tpData.bTouchOnTrigger)
    {
        // Was the document area selected?
        mbTouchDoc = tpData.point.y < GetPageViewBottom(mpManualTocPanel);
        if (mbTouchDoc)
        {
            mTouchOnDispY = tpData.point.y;
            mTouchOnDocY =  mPageView.GetViewPosY();
            mPreDragDocY = mTouchOnDispY;
            mDocDragDir = 0;
            mMaxMovePosY = mTouchOnDispY;
            ResetScrollInertia();

            mInputKind = INPUTKIND_DOC;
            mPageView.SetScrollStart(true);
            return true;
        }
    }

    if (mInputKind != INPUTKIND_DOC)
    {
        return false;
    }

    if (mbTouchDoc) // Was the document area selected when the Touch Panel was tapped?
    {
        if (tpData.bTouchOn)    // Is the Touch Panel being tapped?
        {
            // Scroll with a dragging operation
            s16 crDragY = tpData.point.y;
            mTouchMoveYs[2] = mTouchMoveYs[1];
            mTouchMoveYs[1] = mTouchMoveYs[0];
            mTouchMoveYs[0] = static_cast<s16>(crDragY - mPreDragDocY);

            if (mTouchMoveYs[0] != 0)
            {
                const s16 ASOBI_VAL = 3;    // Amount of play when dragging in the opposite direction
                s16 crDirection = static_cast<s16>(mTouchMoveYs[0] < 0 ? -1: 1);
                if (mDocDragDir == 0)   // When the dragging direction is undefined
                {
                    mDocDragDir = crDirection;
                    mMaxMovePosY = crDragY;
                }
                else if (mDocDragDir != crDirection)    // Has the dragging direction changed?
                {
                    if (Abs(crDragY - mMaxMovePosY) <= ASOBI_VAL) // Within the range of play
                    {
                        // Set the current position equal to the previous position (make it unchanged)
                        crDragY = mPreDragDocY;
                    }
                    else
                    {
                        mDocDragDir = crDirection;
                        // To prevent unnatural dragging, adjust the y-coordinate when the Touch Screen is tapped
                        mTouchOnDispY = static_cast<s16>(mMaxMovePosY + (mTouchMoveYs[0] < 0 ? -ASOBI_VAL: ASOBI_VAL));
                        mTouchOnDocY =  mPageView.GetViewPosY();

                        mMaxMovePosY = crDragY;
                        mPageView.SetScrollStart(true);
                    }
                }
                else
                {
                    mMaxMovePosY = crDragY;
                }
            }

            // Add the difference between the current position and the dragging start position to the bar position when the dragging started to find the new bar position
            // 
            const int newDocY = mTouchOnDocY + mTouchOnDispY - crDragY;

            mPageView.SetNextViewPosY(static_cast<s16>(
                Min(
                    Max(
                        newDocY,
                        0),
                    mPageView.GetScrollHeight() - detail::WHOLEPAGEVIEW_HEIGHT)));

            mPreDragDocY = tpData.point.y; // Update the dragging position
            return true;
        }

        if (tpData.bTouchOffTrigger)
        {
            // Decide on the scrolling inertia when the Touch Screen is released
            if (mTouchMoveYs[0] != 0)
            {
                s32 moveY = 0;
                for (int i = 0; i < 3; ++i)
                {
                    moveY += mTouchMoveYs[i];
                }

                bool bMinus = moveY < 0;
                moveY = Min(Abs(moveY) / 3, long(mDocScrollMax)); 
                mTouchMoveY = static_cast<s16>((bMinus ? -moveY: moveY) * 16);
            }
            else
            {
                mTouchMoveY = 0;
            }
        }
        // Flow downwards unchanged
    }

    // Perform inertial scrolling
    if (mTouchMoveY != 0)
    {
        mPageView.SetNextViewPosY(static_cast<s16>(
            Min(
                Max(
                    mPageView.GetViewPosY() - mTouchMoveY / 16,
                    0),
                mPageView.GetScrollHeight() - detail::WHOLEPAGEVIEW_HEIGHT)));
        bool bMinus = mTouchMoveY < 0;
        int touchMoveY = Abs(mTouchMoveY);
        touchMoveY = Max(0, touchMoveY - mDocScrollLoss);
        mTouchMoveY = static_cast<s16>(bMinus ? -touchMoveY: touchMoveY);

        if (mPageView.GetViewPosY() == mPageView.GetNextViewPosY())
        {
            mTouchMoveY = 0;
        }
    }

    // Clear the input type if the inertial scroll value is 0
    if (mTouchMoveY == 0)
    {
        mInputKind = INPUTKIND_NONE;
    }

    return true;
}

bool
ManualView::OnKeyScroll()
{
    if (mpManualTocPanel->IsBusy())
    {
        return false;
    }

    bool bUpScroll = false;
    bool bDownScroll = false;
    bool bHandled = false;

    if (mInputKind == INPUTKIND_NONE)
    {
        if (mGamePad.IsTrigger(PAD_KEY_UP))
        {
            bUpScroll = true;
        }
        else if (mGamePad.IsTrigger(PAD_KEY_DOWN))
        {
            bDownScroll = true;
        }

        if (bUpScroll || bDownScroll)
        {
            if (mPageView.CheckScroll(bUpScroll, bDownScroll))
            {
                mInputKind = INPUTKIND_KEYSCROLL;
                bHandled = true;
                mpScrollButton->SetButtonState(bUpScroll, bDownScroll);
                mPageView.SetScrollStart(true);
            }
            else
            {
                mSndMgr.startSe(TWL_MAN_SE_INVALID);
            }
        }
    }
    else if (mInputKind == INPUTKIND_KEYSCROLL)
    {
        if (mGamePad.IsPress(PAD_KEY_UP))
        {
            bUpScroll = true;
        }
        else if (mGamePad.IsPress(PAD_KEY_DOWN))
        {
            bDownScroll = true;
        }

        if (bUpScroll || bDownScroll)
        {
            bHandled = true;
            mPageView.CheckScroll(bUpScroll, bDownScroll);
        }
        else
        {
            mInputKind = INPUTKIND_NONE;
            mpScrollButton->SetButtonState(false, false);
        }
    }

    return bHandled;
}

bool
ManualView::OnKeyEvent()
{
    if (mpManualTocPanel->IsBusy())
    {
        return false;
    }

    bool bProcess = false;

#if defined(DOC_SCROLL_DEBUG)
    if (mGamePad.IsPress(PAD_BUTTON_L))
    {
        if (mGamePad.IsTrigger(PAD_KEY_UP))
        {
            mDocScrollMax++;
            NTMV_REPORT("Maximum number of dots to scroll %d\n", mDocScrollMax);
        }
        else if (mGamePad.IsTrigger(PAD_KEY_DOWN))
        {
            mDocScrollMax  = static_cast<s16>(Max(0, mDocScrollMax - 1));
            NTMV_REPORT("Maximum number of dots to scroll %d\n", mDocScrollMax);
        }

        if (mGamePad.IsTrigger(PAD_BUTTON_X))
        {
            mDocScrollLoss++;
            NTMV_REPORT("Scroll attenuation %d\n", mDocScrollLoss);
        }
        else if (mGamePad.IsTrigger(PAD_BUTTON_B))
        {
            mDocScrollLoss  = static_cast<s16>(Max(0, mDocScrollLoss - 1));
            NTMV_REPORT("Scroll attenuation %d\n", mDocScrollLoss);
        }
    }
    else
#endif  // #if defined(DOC_SCROLL_DEBUG)

    if (mGamePad.IsTrigger(PAD_KEY_LEFT))
    {
        if (MANUALTOCEVENT_NONE != mpManualTocPanel->MovePrevItem())
        {
            LoadSelectedPage();
            bProcess = true;
        }
    }
    else if (mGamePad.IsTrigger(PAD_KEY_RIGHT))
    {
        if (MANUALTOCEVENT_NONE != mpManualTocPanel->MoveNextItem())
        {
            LoadSelectedPage();
            bProcess = true;
        }
    }
    else if (mGamePad.IsTrigger(PAD_BUTTON_SELECT))
    {
        bool bHelpSelected;
        bProcess = mpManualTocPanel->PressHelpButton(&bHelpSelected);
        if (bProcess)
        {
            ProcessHelpButton(bHelpSelected);
        }
    }
    else if (mGamePad.IsTrigger(PAD_BUTTON_A))
    {
        SetUpDispScrollIndicatorState(mpManualTocPanel->PressTocOpenButton());
        bProcess = true;
    }
    else if (mGamePad.IsTrigger(PAD_BUTTON_START))
    {
        ProcessExitButton();
        bProcess = true;
    }

    return bProcess;
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::ProcessHelpButton

  Description:  Processes the event when the Help button is pressed.

  Arguments:    bSelected: TRUE when the Help button is selected

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::ProcessHelpButton(bool bSelected)
{
    if (bSelected)    // Is the Help button selected?
    {
        LoadHelpPage();
    }
    else if (-1 != mpManualTocPanel->GetSelectedHeaderIdx())   // Is a header selected?
    {
        LoadSelectedPage();
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::ProcessExitButton

  Description:  Processes the event when the exit button is pressed.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::ProcessExitButton()
{
    mpManualTocPanel->PressExitButton();
    mbExit = true;
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::InitFont

  Description:  Initializes fonts.

  Arguments:    fonts: Array of font data pointers

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::InitFont(const NNSG2dFont *const fonts[])
{
    for (int i = 0; i < FONTSIZE_MAX; ++i)
    {
        mFonts[i] = fonts[i];

        NTMV_LOG("Load font [%d] baseline %d, cell width %d, cell height %d\n",
            i,
            NNS_G2dFontGetBaselinePos(mFonts[i]),
            NNS_G2dFontGetCellWidth(mFonts[i]),
            NNS_G2dFontGetCellHeight(mFonts[i]));
    }

    mPageView.SetFont(mFonts);
}

void
ManualView::InitVRAM()
{
    //---------------------------------------------------------------------------
    // All VRAM banks to LCDC
    //---------------------------------------------------------------------------
    GX_SetBankForLCDC(GX_VRAM_LCDC_ALL);

    //---------------------------------------------------------------------------
    // Clear all LCDC space
    //---------------------------------------------------------------------------
    MI_CpuClearFast((void *)HW_LCDC_VRAM, HW_LCDC_VRAM_SIZE);

    //---------------------------------------------------------------------------
    // Disable the banks on LCDC
    //---------------------------------------------------------------------------
    (void)GX_DisableBankForLCDC();

    MI_CpuFillFast((void *)HW_OAM, 192, HW_OAM_SIZE);   // Clear OAM
    MI_CpuClearFast((void *)HW_PLTT, HW_PLTT_SIZE);     // Clear the standard palette

    MI_CpuFillFast((void *)HW_DB_OAM, 192, HW_DB_OAM_SIZE);     // Clear OAM
    MI_CpuClearFast((void *)HW_DB_PLTT, HW_DB_PLTT_SIZE);       // Clear the standard palette
}

void
ManualView::InitDisplay()
{
    /*
        Upper screen BG  | 128k VRAM-C               |
                   |---------------------------|
                   | Manual screen       128k |
                   |---------------------------|

               OBJ | 128k VRAM-D               |

        Lower screen BG  | 256k VRAM-A,B             |
                   |---------------------------|
                   | Manual screen       128k |
                   |---------------------------|
                   | For the Contents page                    |
                   | Scroll indicator 8k |
                   |---------------------------|
                   | Contents section background       8k |
                   |---------------------------|
                   | Contents page background               4k |
                   |---------------------------|
                   | Unused                44k |
                   |---------------------------|
                   | Character data for the Contents page   32k |
                   |---------------------------|
                   | Unused                32k |
                   |---------------------------|

               OBJ |  64k VRAM-E               |
    */

    // Settings for the LCD output target
    //   Up: Sub-engine (2D)
    //   Down: Main engine (2D/3D)
    GX_SetDispSelect(GX_DISP_SELECT_SUB_MAIN);

    // Configure the main screen
    //   Graphics display
    //   BG mode 3 (BG0 = text, BG1 = text, BG2 = text, BG3 = affine extended BG)
    GX_SetGraphicsMode(GX_DISPMODE_GRAPHICS, GX_BGMODE_3, GX_BG0_AS_2D);

    int mainVisiblePlane = 0;           // Select the display

    // Sub-screen settings
    //   BG mode 3 (BG0 = text, BG1 = text, BG2 = text, BG3 = affine extended BG)
    GXS_SetGraphicsMode(GX_BGMODE_3);

    int subVisiblePlane = 0;            // Select the display

    // Configure the main BG
    GX_SetBankForBG(GX_VRAM_BG_256_AB); // Assign VRAM-A, B banks to the BG
    
    GX_SetBGScrOffset(GX_BGSCROFFSET_0x20000);      // Set screen offset value
    GX_SetBGCharOffset(GX_BGCHAROFFSET_0x30000);    // Set character offset

    //     BG0 settings
    // mainVisiblePlane |= GX_PLANEMASK_BG0;    // Do not display initially
    G2_SetBG0Priority(0);               // Set BG0 to the highest priority
    G2_BG0Mosaic(false);                // Do not apply mosaic effects to BG0
    G2_SetBG0Control(
        GX_BG_SCRSIZE_TEXT_512x512,     // Although the size of the data itself is 512x256, set it to 512x512 so that we can scroll horizontally and vertically
        GX_BG_COLORMODE_16,
        GX_BG_SCRBASE_0x0000,
        GX_BG_CHARBASE_0x00000,
        GX_BG_EXTPLTT_01);

    //     BG1 settings
    // mainVisiblePlane |= GX_PLANEMASK_BG1;    // Do not display initially
    G2_SetBG1Priority(1);               // Set BG1 to the second-highest priority
    G2_BG1Mosaic(false);                // Do not apply mosaic effects to BG1
    G2_SetBG1Control(
        GX_BG_SCRSIZE_TEXT_512x512,     // Although the size of the data itself is 512x256, set it to 512x512 so that we can scroll horizontally and vertically
        GX_BG_COLORMODE_16,             // 16-color mode
        GX_BG_SCRBASE_0x2000,
        GX_BG_CHARBASE_0x00000,
        GX_BG_EXTPLTT_01);

    //     BG2 settings
    // mainVisiblePlane |= GX_PLANEMASK_BG2;    // Do not display initially
    G2_SetBG2Priority(2);               // Set BG2 to the third-highest priority
    G2_BG2Mosaic(false);                // Do not apply mosaic effects to BG2
    G2_SetBG2ControlText(
        GX_BG_SCRSIZE_TEXT_256x512,     // Although the size of the data itself is 256x256, set it to 256x512 so that we can scroll vertically
        GX_BG_COLORMODE_16,             // 16-color mode
        GX_BG_SCRBASE_0x4000,
        GX_BG_CHARBASE_0x00000);

    //     BG3 settings
    mainVisiblePlane |= GX_PLANEMASK_BG3;
    G2_SetBG3Priority(3);               // Set BG3 to the fourth-highest priority
    G2_BG3Mosaic(false);                // Do not apply mosaic to BG3
    G2_SetBG3ControlDCBmp(              // Direct color bitmap
        GX_BG_SCRSIZE_DCBMP_256x256,    //   Set the screen size to 256x256 pixels
        GX_BG_AREAOVER_REPEAT,          //   Area over processing
        GX_BG_BMPSCRBASE_0x00000);      //   Screen base block: 0x00000

    // Sub-BG settings
    GX_SetBankForSubBG(GX_VRAM_SUB_BG_128_C);   // Assign VRAM-C banks to the BG
    
    //     BG3 settings
    subVisiblePlane |= GX_PLANEMASK_BG3;
    G2S_SetBG3Priority(0);              // Set BG3 priority to top
    G2S_BG3Mosaic(false);               // Do not apply mosaic to BG3
    G2S_SetBG3ControlDCBmp(             // Direct color bitmap
        GX_BG_SCRSIZE_DCBMP_256x256,    //   Set the screen size to 256x256 pixels
        GX_BG_AREAOVER_REPEAT,          //   Area over processing
        GX_BG_BMPSCRBASE_0x00000);      //   Screen base block: 0x00000

    // Main OBJ settings
    mainVisiblePlane |= GX_PLANEMASK_OBJ;
    GX_SetBankForOBJ(GX_VRAM_OBJ_64_E); // Assign VRAM-E banks to OBJ
    GX_SetOBJVRamModeChar(ObjManager::GetOBJVRamMappingMode());
    GX_SetBankForOBJExtPltt(GX_VRAM_OBJEXTPLTT_8_F);
    
    // Sub-OBJ settings
    subVisiblePlane |= GX_PLANEMASK_OBJ;
    GX_SetBankForSubOBJ(GX_VRAM_SUB_OBJ_128_D); // Assign VRAM-D banks to OBJ       
    GXS_SetOBJVRamModeChar(ObjManager::GetOBJVRamMappingMode());
    GX_SetBankForSubOBJExtPltt(GX_VRAM_SUB_OBJEXTPLTT_0_I);

    // Select the main display
    GX_SetVisiblePlane(mainVisiblePlane);

    // Select the sub-display
    GXS_SetVisiblePlane(subVisiblePlane);

    // Set the maximum brightness when starting to display
    GX_SetMasterBrightness(FADER_BRIGHTNESS_MAX);
    GXS_SetMasterBrightness(FADER_BRIGHTNESS_MAX);

    SetAlphaBlendForTocUI();    // Alpha blending settings

    // Window settings
    // Used to scroll manual pages horizontally.
    {
        const int inPlanes = GX_WND_PLANEMASK_BG3;
        const int outPlanes = GX_WND_PLANEMASK_BG0 | GX_WND_PLANEMASK_BG1 | GX_WND_PLANEMASK_BG2 | GX_WND_PLANEMASK_OBJ;
        const bool colorEffect = true;

        G2_SetWnd0InsidePlane(inPlanes, colorEffect);
        G2_SetWndOutsidePlane(outPlanes, colorEffect);
        G2S_SetWnd0InsidePlane(inPlanes, colorEffect);
        G2S_SetWndOutsidePlane(outPlanes, colorEffect);
    }
}

//==========================================================================
//      Scroll-related
//==========================================================================

/*---------------------------------------------------------------------------*
  Name:         ManualView::LoadNtpg

  Description:  Loads ntpg.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::LoadNtpg(
    const char*         ntpgFileName,
    ResourceAccessor*   pResAccessor,
    bool                bPrev
)
{
    NNS_NULL_ASSERT(ntpgFileName);

    // Change gradually to pure white
    for (int cnt = 0; cnt < PAGE_FADER_FRAME_COUNT; ++cnt)
    {
        // Set the last frame to the maximum brightness
        const int bright = cnt * FADER_BRIGHTNESS_MAX / (PAGE_FADER_FRAME_COUNT - 1);

        Draw();

        OS_WaitVBlankIntr();    // V-Blank wait

        SetDocBrightness(bright);

        int posX = 0;
        if (bright > PAGE_FADER_MOVE_START_FRAME)
        {
            posX = (GX_LCD_SIZE_X / 4) * (bright - PAGE_FADER_MOVE_START_FRAME) / (FADER_BRIGHTNESS_MAX - PAGE_FADER_MOVE_START_FRAME);
        }
        if (bPrev)
        {
            posX = -posX;
        }
        TransferDrawData(static_cast<s16>(posX));
    }

    DumpHeap();

    ResetScrollInertia();

    mpScrollButton->SetContentHeight(0);
    mpScrollButton->Update(0);
    
    for (int i = 0; i < DISPTYPE_MAX; ++i)
    {
        mpScrollIndicators[i]->SetContentHeight(0);
        mpScrollIndicators[i]->UpdateViewY(0);
    }

    ClearNtpg();

    // Load the layout resource
    if (const void* lytRes = pResAccessor->GetLocalizeResource(res::RESOURCETYPE_PAGE, ntpgFileName))
    {
        const ObjectConstructor& objectConstructor(&mAllocator);

        mpPage = objectConstructor.NewObj<Page>();
        NNS_NULL_ASSERT(mpPage);

        mpPage->Init(objectConstructor, lytRes, pResAccessor);
        mpPage->SetPageHeightMin(detail::PAGEHEIGHT_MIN);

        mPageView.SetPage(mpPage);

        mpScrollButton->SetContentHeight(mPageView.GetPage()->GetPageSize().height);
        mpScrollButton->Update(mPageView.GetViewPosY());
        for (int i = 0; i < DISPTYPE_MAX; ++i)
        {
            mpScrollIndicators[i]->SetContentHeight(mPageView.GetPage()->GetPageSize().height);
            mpScrollIndicators[i]->UpdateViewY(mPageView.GetViewPosY());
        }

        NTMV_REPORT("Load bntpg file. - [%s]\n", ntpgFileName);
    }
    else
    {
        NTMV_REPORT("can't load bntpg file. - [%s]\n", ntpgFileName);
    }

    OS_WaitVBlankIntr();    // V-Blank wait

    // Configure the manual page's background color as the backdrop color
    SetBackDropColor(mpPage->GetBackColor());

    for (int cnt = PAGE_FADER_FRAME_COUNT; cnt > 0;)
    {
        --cnt;
        const int bright = cnt * FADER_BRIGHTNESS_MAX / (PAGE_FADER_FRAME_COUNT - 1);

        Draw();

        OS_WaitVBlankIntr();    // V-Blank wait

        SetDocBrightness(bright);
        int posX = 0;
        if (bright > PAGE_FADER_MOVE_START_FRAME)
        {
            posX = - GX_LCD_SIZE_X / 4 * (bright - PAGE_FADER_MOVE_START_FRAME) / (FADER_BRIGHTNESS_MAX - PAGE_FADER_MOVE_START_FRAME);
        }
        if (bPrev)
        {
            posX = -posX;
        }
        TransferDrawData(static_cast<s16>(posX));
    }

    if (! mpManualTocPanel->IsOpened())
    {
        SetAlphaBlendForTocUI();
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::ClearNtpg

  Description:  Clears ntpg.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::ClearNtpg()
{
    if (mpPage)
    {
        const ObjectConstructor& objectConstructor(&mAllocator);
        objectConstructor.DeleteObj(mpPage);
        mpPage = 0;
        mPageView.SetPage(0);
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::SetLanguage

  Description:  Sets the language used to display the manual.
  
  Arguments:    lang:           Language to use to display the manual (iso639)

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::SetLanguage(u16 lang)
{
    u16 wkLang;
    if (0 != lang)
    {
        wkLang = lang;
    }
    else
    {
        /// Search for the Contents file using the system language.
        /// If it exists, the language configured by the System Settings will be used for the manual
        /// If it does not exist, the language initially specified in the language information is used.
        u8 langCode = 0;
        if (OS_IsRunOnTwl())
        {
            #ifdef SDK_TWL
                OSOwnerInfoEx ownerInfoEx;
                OS_GetOwnerInfoEx(&ownerInfoEx);
                langCode = ownerInfoEx.language;
            #endif
        }
        else
        {
            #ifndef SDK_TWLLTD
                OSOwnerInfo ownerInfo;
                OS_GetOwnerInfo(&ownerInfo);
                langCode = ownerInfo.language;
            #endif
        }

        static const u16 langs[] =
        {
            'ja',   // 0: Japanese
            'en',   // 1: English
            'fr',   // 2: French
            'de',   // 3: German
            'it',   // 4: Italian
            'es',   // 5: Spanish
            'zh',   // 6: Chinese
            'ko',   // 7: Korean
        };

        wkLang = langs[langCode];
    }

    // Does the Contents file exist?
    if (! mManArcAccessor.Exists(res::RESOURCETYPE_MANUALTOC, wkLang, NTMV_M2D_MTOCFILENAME))
    {
        // Get the language initially specified from the language information file
        LangInfo langInfo;
        const bool bSuccess = LoadLangInfoFile(&langInfo, NTMV_M2D_LANGINFOFILENAME, &mManArcAccessor);
        NNS_ASSERT(bSuccess);
        NNS_ASSERT(langInfo.count > 0);
        wkLang = langInfo.langs[0];
    }

    mManArcAccessor.SetLanguage(wkLang);
    mUIResAccessor.SetLanguage(wkLang);
}

/*---------------------------------------------------------------------------*
  Name:         LoadStartPages

  Description:  Loads the starting page for the upper and lower screens.

                Loads index_top.rlyt to the upper screen and index_bottom.rlyt to the lower screen.
                
                If one does not exist, it stays hidden.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::LoadStartPages()
{
    ObjectConstructor objectConstructor(&mAllocator);
    mpManualToc = objectConstructor.NewObj<ManualTableOfContents>();
    NNS_NULL_ASSERT(mpManualToc);

    const bool bSuccess = mpManualToc->LoadFile(NTMV_M2D_MTOCFILENAME, &mManArcAccessor, objectConstructor);
    NNS_ASSERT(bSuccess);

    mpManualTocPanel->SetupManualTableOfContents(mpManualToc);

    if (-1 != mpManualTocPanel->GetSelectedHeaderIdx())   // Is a header selected?
    {
        LoadSelectedPage();
    }
    else
    {
        LoadHelpPage();
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::LoadSelectedPage

  Description:  Loads the selected page.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::LoadSelectedPage()
{
    // if (mpManualToc->GetHeaderNum() > 1)
    {
        const s16 headerIdx = mpManualTocPanel->GetSelectedHeaderIdx();
        const s16 chapterIdx = mpManualTocPanel->GetSelectedChapterIdx();
        const ManualHeader& header = mpManualToc->GetHeaders()[headerIdx];
        const char *const fileName = 
            chapterIdx == -1 ?
                header.GetHeaderFileName():
                header.GetChaptetFileNames()[mpManualTocPanel->GetSelectedChapterIdx()];

        s32 newPageOrder = (headerIdx + 1) << 16 | (chapterIdx + 1);
        LoadNtpg(fileName, &mManArcAccessor, mPageOrder > newPageOrder);

        mpPageHeaderPanel->SetText(
            header.GetHeaderFullText(),
            static_cast<u16>(headerIdx));

        mPageOrder = newPageOrder;
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::LoadHelpPage

  Description:  Loads the Help page.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::LoadHelpPage()
{
    LoadNtpg(NTMV_M2D_HELPPAGE_FILENAME, &mUIResAccessor, true);

    mpPageHeaderPanel->SetText(
        mPageView.GetPageHeaderText(),
        static_cast<u16>(-1));

    mPageOrder = 0;
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::ResetHardware

  Description:  Resets hardware settings for shutdown processing.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::ResetHardware()
{
    {
        const u32 errCode = TP_RequestAutoSamplingStop();
        NNS_ASSERT(errCode == 0);
    }

    // Stop all sounds
    NNS_SndStopSoundAll();

    // Turn off LCD output
    GX_DispOff();
    GXS_DispOff();

    // Restore the original DMA settings
    if (mbSetDMA)
    {
        GX_SetDefaultDMA(GX_DMA_NOT_USE);
    }

#if 1
    GX_Init();  // Reset GX settings to their initial state
    // Restore the state of the V-Blank interrupt request generation enable flag to its original state
    if (mbEnabledVBlankIntr)
    {
        GX_VBlankIntr(true);
    }
#else
    // Disable all bank assignments
    GX_DisableBankForBG();
    GX_DisableBankForOBJ();
    GX_DisableBankForBGExtPltt();
    GX_DisableBankForOBJExtPltt();
    GX_DisableBankForTex();
    GX_DisableBankForTexPltt();
    GX_DisableBankForClearImage();
    GX_DisableBankForSubBG();
    GX_DisableBankForSubOBJ();
    GX_DisableBankForSubBGExtPltt();
    GX_DisableBankForSubOBJExtPltt();
    GX_DisableBankForARM7();
    GX_DisableBankForLCDC();
#endif

    if (OS_IsRunOnTwl())
    {
        #ifdef SDK_TWL
            if (! mbPMAutoExit)
            {
                // Revert setting
                PM_SetAutoExit(false);
            }
        #endif
    }

    if (mbMountManual)
    {
        const BOOL bSuccess = NNS_FndUnmountArchive(&mManualArc);
        NNS_WARNING(bSuccess, "fail NNS_FndUnmountArchive().\n");
    }

    if (mbMountUIRes)
    {
        const BOOL bSuccess = NNS_FndUnmountArchive(&mUiResArc);
        NNS_WARNING(bSuccess, "fail NNS_FndUnmountArchive().\n");
    }
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::ResetScrollInertia

  Description:  Resets the manual's scrolling inertia.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::ResetScrollInertia()
{
    // Stop inertial scrolling
    for (int i = 0; i < 3; ++i)
    {
        mTouchMoveYs[i] = 0;
    }
    mTouchMoveY = 0;
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::SetDocBrightness

  Description:  Sets the document screen's brightness.

  Arguments:    brightness: Value for adjusting the brightness (between -16 and 16)

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::SetDocBrightness(int brightness)
{
    if (! mpManualTocPanel->IsOpened())
    {
        G2_SetBlendBrightness(GX_BLEND_PLANEMASK_BG3 | GX_BLEND_PLANEMASK_BD, brightness);
    }
    G2S_SetBlendBrightness(GX_BLEND_PLANEMASK_BG3 | GX_BLEND_PLANEMASK_BD, brightness);
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::SetDocFade

  Description:  Sets the document screen's brightness.

  Arguments:    brightness: Value for adjusting the brightness (between -16 and 16)

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::SetDocFade(int posX)
{
    int x1 = 0, x2 = GX_LCD_SIZE_X;

    if (posX > 0)
    {
        x2 = GX_LCD_SIZE_X - posX;
    }
    else
    {
        x1 = -posX;
    }

    if (! mpManualTocPanel->IsOpened())
    {
        G2_SetWnd0Position(x1, 0, x2, detail::DOWNPAGEVIEW_HEIGHT);
    }
    G2S_SetWnd0Position(x1, detail::PAGEHEADERVIEW_HEIGHT, x2, GX_LCD_SIZE_Y);
}

/*---------------------------------------------------------------------------*
  Name:         ManualView::SetUpDispScrollIndicatorState

  Description:  Sets the position to display the (downward-pointing) scroll indicator on the upper screen.

  Arguments:    bClosed: TRUE when the Contents page is open

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::SetUpDispScrollIndicatorState(bool bOpened)
{
    const s16 height = static_cast<s16>(bOpened ? detail::UPPAGEVIEW_HEIGHT: detail::WHOLEPAGEVIEW_HEIGHT);
    Point pos = mpScrollIndicators[DISPTYPE_UP]->GetPosition();
    pos.y = static_cast<s16>(detail::PAGEHEADERVIEW_HEIGHT + height / 2);
    mpScrollIndicators[DISPTYPE_UP]->SetPosition(pos);
    mpScrollIndicators[DISPTYPE_UP]->SetHeight(height);
}

#if defined(NTMV_M2D_DEVELOP)
/*---------------------------------------------------------------------------*
  Name:         ManualView::DumpHeap

  Description:  Dumps the heap.
                Currently, this is the heap referenced by the allocator.

  Arguments:    None.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ManualView::DumpHeap()
{
    NNS_FndDumpHeap((NNSFndHeapHandle)mAllocator.pHeap);
}
#endif  // #if defined(NTMV_M2D_DEVELOP)

}   // namespace m2d
}   // namespace ntmv
