/*--------------------------------------------------------------------------
  Project:  Manual 2D Library - m2d
  File:     m2d_common.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/misc.h>
#include <ntmv/m2d/debug.h>
#include <ntmv/m2d/inlines.h>
#include <ntmv/m2d/common.h>
#include <ntmv/m2d/texMap.h>
#include <ntmv/m2d/drawInfo.h>
#include <cstring>
#include <new>

namespace ntmv
{
namespace m2d
{

namespace
{

/*---------------------------------------------------------------------------*
  Name:         MakeSelectedColor

  Description:  Generates the color used when a selection is made.

  Arguments:    color:  The original color to generate

  Returns:      Returns the selection color.
 *---------------------------------------------------------------------------*/
const int   IMAGE_SUB_PRAM  = 10;

inline
GXRgba
MakeSelectedColor(GXRgba color)
{
    s32 redb   = (color & GX_RGBA_R_MASK) >> GX_RGBA_R_SHIFT;
    s32 greenb = (color & GX_RGBA_G_MASK) >> GX_RGBA_G_SHIFT;
    s32 blueb  = (color & GX_RGBA_B_MASK) >> GX_RGBA_B_SHIFT;
    s32 alphab = (color & GX_RGBA_A_MASK) >> GX_RGBA_A_SHIFT;

    if (redb <= IMAGE_SUB_PRAM)
    {
        redb = 0;
    }
    else
    {
        redb -= IMAGE_SUB_PRAM;
    }
    if (greenb <= IMAGE_SUB_PRAM)
    {
        greenb = 0;
    }
    else
    {
        greenb -= IMAGE_SUB_PRAM;
    }
    if (blueb <= IMAGE_SUB_PRAM)
    {
        blueb = 0;
    }
    else
    {
        blueb -= IMAGE_SUB_PRAM;
    }

    return GX_RGBA(redb, greenb, blueb, alphab);
}

}   // namespace

namespace detail
{

bool
TestFileHeader(const res::BinaryFileHeader& fileHeader, u32 testSig)
{
    return  GetSignatureInt(fileHeader.signature) == testSig
        &&  fileHeader.byteOrder == res::ByteOrderMark;
}

void
FillColor(
    const DrawInfo& drawInfo,
    Point           pt,
    Size            size,
    GXRgb           color
)
{
    const Rect drawRect = GetDrawRect(pt, size, drawInfo.GetInvalidateRect());
    if (drawRect.GetWidth() == 0) // Quit when there is no rendering area
    {
        return;
    }

    for (int y = drawRect.top; y < drawRect.bottom; ++y)
    {
        u16 *const lineBuf = GetLineBuf(drawInfo, static_cast<s16>(pt.y + y));
        for (int x = drawRect.left; x < drawRect.right; ++x)
        {
            lineBuf[pt.x + x] = color;
        }
    }
}

const Rect
GetDrawRect(
    const Point             pt,
    const Size              texSz,
    const Rect&             canvasRect
)
{
    Rect rect(0, 0, 0, 0);

    // Find the horizontal rendering range
    const int startX = Max(canvasRect.left - pt.x, 0);
    const int endX   = Min(canvasRect.right - pt.x, int(texSz.width ));
    if (startX < endX)  // There is a rendering area
    {
        // Find the vertical rendering range
        const int startY = Max(canvasRect.top - pt.y, 0);
        const int endY   = Min(canvasRect.bottom - pt.y, int(texSz.height));
        if (startY < endY) // There is a rendering area
        {
            rect.left   = static_cast<s16>(startX);
            rect.right  = static_cast<s16>(endX);
            rect.top    = static_cast<s16>(startY);
            rect.bottom = static_cast<s16>(endY);
        }
    }

    return rect;
}

u16*
GetLineBuf(
    const DrawInfo& drawInfo,
    s16             y
)
{
    const Size frameBufSize = drawInfo.GetFrameBufSize();
    return drawInfo.GetFrameBuf()
            + (y & (frameBufSize.height - 1)) * frameBufSize.width;
}

#if defined(NTMV_M2D_USE_DIRECTTEXTURE)
const u16*
GetLineBuf(
    const TexMap&   texMap,
    s16             y
)
{
    const Size texSize = texMap.GetSize();
    NNS_ASSERT(y < texSize.height);

    const u32 strideValue = texSize.width * sizeof(u16);

    return reinterpret_cast<const u16*>(
          static_cast<const u8*>(texMap.GetImage()->GetImage())
        + y * strideValue
    );
}
#else   // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
/*---------------------------------------------------------------------------*
  Name:         TexelPicker::Create

  Description:  Initializes a TexelPicker object.

  Arguments:    pTexelPicker:  Pointer to the TexelPicker object to initialize.
                pTexMap:       Pointer to the TexMap object to pick up.

  Returns:      Returns a pointer to a TexelPicker object.
 *---------------------------------------------------------------------------*/
TexelPicker*
TexelPicker::Create(
    TexelPicker*    pTexelPicker,
    const TexMap*   pTexMap
)
{
    const ImageHeader *const pImageHd = pTexMap->GetTexture()->GetImageHeader();

    // Initialize a TexelPicker instance using the placement new syntax
    switch (pImageHd->texelFormat)
    {
    case GX_TEXFMT_DIRECT:      // Direct format
        new (pTexelPicker) DirectTexelPicker(pTexMap);
        break;

    case GX_TEXFMT_PLTT256:     // 256-color format
        new (pTexelPicker) Palette256TexelPicker(pTexMap);
        break;
    default:
        NNS_ASSERT(false);
    }

    return pTexelPicker;
}

/*---------------------------------------------------------------------------*
  Name:         TexelPicker::Get

  Description:  Picks up a texel.

  Arguments:    pt:  Position of the texel to get

  Returns:      Returns the texel that was picked up.
 *---------------------------------------------------------------------------*/
u16
TexelPicker::Get(Point /* pt */) const
{
    return 0;
}

/*---------------------------------------------------------------------------*
  Name:         DirectTexelPicker::DirectTexelPicker

  Description:  Constructor.

  Arguments:    pTexMap:  Pointer to the TexMap object to pick up.

  Returns:      None.
 *---------------------------------------------------------------------------*/
DirectTexelPicker::DirectTexelPicker(const TexMap* pTexMap)
{
    mTexSize = pTexMap->GetSize();
    const ImageHeader *const pImageHd = pTexMap->GetTexture()->GetImageHeader();
    NNS_ASSERT(pImageHd->texelFormat == GX_TEXFMT_DIRECT);

    mpImage = pImageHd + 1;
    mHStrideBytes = static_cast<u32>(mTexSize.width << 1); // = * sizeof(u16)
}

/*---------------------------------------------------------------------------*
  Name:         DirectTexelPicker::Get

  Description:  Picks up a texel.

  Arguments:    pt:  Position of the texel to get

  Returns:      Returns the texel that was picked up.
 *---------------------------------------------------------------------------*/
u16
DirectTexelPicker::Get(Point pt) const
{
    NNS_ASSERT(pt.y < mTexSize.height);

    const u16* lineBuf = reinterpret_cast<const u16*>(
          static_cast<const u8*>(mpImage) + pt.y * mHStrideBytes);

    return lineBuf[pt.x];
}

/*---------------------------------------------------------------------------*
  Name:         Palette256TexelPicker::Palette256TexelPicker

  Description:  Constructor.

  Arguments:    pTexMap:  Pointer to the TexMap object to pick up.

  Returns:      None.
 *---------------------------------------------------------------------------*/
Palette256TexelPicker::Palette256TexelPicker(const TexMap* pTexMap)
{
    mTexSize = pTexMap->GetSize();
    const ImageHeader *const pImageHd = pTexMap->GetTexture()->GetImageHeader();
    NNS_ASSERT(pImageHd->texelFormat == GX_TEXFMT_PLTT256);

    mpPalette = reinterpret_cast<const u16*>(pImageHd + 1);
    mpImage = mpPalette + pImageHd->paletteEntryNum;
    mHStrideBytes = mTexSize.width;
    mbHasAlphaPal = pImageHd->bHasAlphaPal;
}

/*---------------------------------------------------------------------------*
  Name:         Palette256TexelPicker::Get

  Description:  Picks up a texel.

  Arguments:    pt:  Position of the texel to get

  Returns:      Returns the texel that was picked up.
 *---------------------------------------------------------------------------*/
u16
Palette256TexelPicker::Get(Point pt) const
{
    NNS_ASSERT(pt.y < mTexSize.height);

    const u8* lineBuf = static_cast<const u8*>(mpImage) + pt.y * mHStrideBytes;

    const u8 idx = lineBuf[pt.x];
    u16 alpha = 0x8000;
    if (idx == 0 && mbHasAlphaPal) // When referencing palette 0, and there's a transparent color.
    {
        alpha = 0;
    }

    return static_cast<u16>(mpPalette[idx] | alpha);
}
#endif  // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)

const TextureFlipInfo&
GetTexutreFlipInfo(u8 textureFlip)
{
    NNS_ASSERT(textureFlip < TEXTUREFLIP_MAX);

    static TextureFlipInfo flipInfos[] =
    {
        { { 0, 0 }, {  1,  1 }, 0 },   // TEXTUREFLIP_NONE
        { { 1, 0 }, { -1,  1 }, 0 },   // TEXTUREFLIP_FLIPH
        { { 0, 1 }, {  1, -1 }, 0 },   // TEXTUREFLIP_FLIPV
        { { 0, 1 }, {  1, -1 }, 1 },   // TEXTUREFLIP_ROTATE90
        { { 1, 1 }, { -1, -1 }, 0 },   // TEXTUREFLIP_ROTATE180
        { { 1, 0 }, { -1,  1 }, 1 },   // TEXTUREFLIP_ROTATE270
    };

    return flipInfos[textureFlip];
}

void
GetTexCoord(
    Point&                  texCd,
    const Size&             texSize,
    const TextureFlipInfo&  flipInfo
)
{
    const int ix = flipInfo.swap ? 1: 0;
    const int iy = ix ^ 1;

    texCd[ix] = static_cast<s16>(flipInfo.coords[ix] * (texSize[ix] - 1));
    texCd[iy] = static_cast<s16>(flipInfo.coords[iy] * (texSize[iy] - 1));
}

/*---------------------------------------------------------------------------*
  Name:         DrawImage

  Description:  Renders a texture.

  Arguments:    drawInfo:  A reference to a DrawInfo object.
                myPt:      Position to render the texture.
                texMap:    A reference to the TexMap object to render.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
DrawImage(
    const DrawInfo& drawInfo,
    Point           myPt,
    const TexMap&   texMap
)
{
    const Rect drawRect = GetDrawRect(myPt, texMap.GetSize(), drawInfo.GetInvalidateRect());
    if (drawRect.GetWidth() == 0) // Quit when there is no rendering area
    {
        return;
    }

    #if ! defined(NTMV_M2D_USE_DIRECTTEXTURE)
        TexelPicker texelPickerMem;
        TexelPicker* pTexelPicker = TexelPicker::Create(&texelPickerMem, &texMap);
    #endif  // #if ! defined(NTMV_M2D_USE_DIRECTTEXTURE)

    
    // Render
    for (s16 y = drawRect.top; y < drawRect.bottom; ++y)
    {
        u16 *const dstLineBuf = GetLineBuf(drawInfo, static_cast<s16>(myPt.y + y));

        #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
            const u16 *const srcLineBuf = GetLineBuf(texMap, y);
        #endif  // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)

        for (s16 x = drawRect.left; x < drawRect.right; ++x)
        {
            #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                const u16 pixel = srcLineBuf[x];
            #else   // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                const u16 pixel = pTexelPicker->Get(Point(x, y));
            #endif  // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)

            if (pixel & 0x8000) // Render if the alpha bit is 1
            {
                dstLineBuf[myPt.x + x] = pixel;
            }
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         DrawImage

  Description:  Renders a texture.

  Arguments:    drawInfo:  A reference to a DrawInfo object.
                pt:        Position to render the texture.
                size:      The texture's rendering size.
                texMap:    A reference to the TexMap object to render.
                texCd:     Texture coordinate for the texture's rendering position.
                flipInfo:  A reference to a TextureFlipInfo object.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
DrawImage(
    const DrawInfo&         drawInfo,
    const Point             pt,
    const Size              size,
    const TexMap&           texMap,
    const Point             texCd,
    const TextureFlipInfo&  flipInfo
)
{
    const Rect drawRect = GetDrawRect(pt, size, drawInfo.GetInvalidateRect());
    if (drawRect.GetWidth() == 0) // Quit when there is no rendering area
    {
        return;
    }

    const int ix = flipInfo.swap ? 1: 0;
    const int iy = ix ^ 1;

    const Size texSize = texMap.GetSize();
    const s16 texMaxCdX = static_cast<s16>(texSize[ix] - 1);    // Maximum valid texture x-coordinate
    const s16 texMaxCdY = static_cast<s16>(texSize[iy] - 1);    // Y
    const s16 dirX = flipInfo.dir[ix];                          // x increment
    const s16 dirY = flipInfo.dir[iy];                          // Y
    const s16 texStartCdX = static_cast<s16>(texCd[ix] + drawRect.left * dirX); // Starting texture x-coordinate
    s16 texCrCdY          = static_cast<s16>(texCd[iy] + drawRect.top  * dirY); // Y

    Point texPt;

    #if ! defined(NTMV_M2D_USE_DIRECTTEXTURE)
        TexelPicker texelPickerMem;
        TexelPicker* pTexelPicker = TexelPicker::Create(&texelPickerMem, &texMap);
    #endif  // #if ! defined(NTMV_M2D_USE_DIRECTTEXTURE)

    for (s16 y = drawRect.top; y < drawRect.bottom; ++y, texCrCdY += dirY)
    {
        u16 *const dstLineBuf = GetLineBuf(drawInfo, static_cast<s16>(pt.y + y));

        texPt[iy] = Min(Max(s16(0), texCrCdY), texMaxCdY);

        s16 texCrCdX = texStartCdX;
        for (int x = drawRect.left; x < drawRect.right; ++x, texCrCdX += dirX)
        {
            texPt[ix] = Min(Max(s16(0), texCrCdX), texMaxCdX);

            #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                const u16 *const srcLineBuf = GetLineBuf(texMap, texPt.y);
                const u16 pixel = srcLineBuf[texPt.x];
            #else   // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                const u16 pixel = pTexelPicker->Get(texPt);
            #endif  // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)

            if (pixel & 0x8000) // Render if the alpha bit is 1
            {
                dstLineBuf[pt.x + x] = pixel;
            }
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         DrawSelectImage

  Description:  Renders a texture in the selected state.

  Arguments:    drawInfo:  A reference to a DrawInfo object.
                pt:        Position to render the texture.
                size:      The texture's rendering size.
                texMap:    A reference to the TexMap object to render.
                texCd:     Texture coordinate for the texture's rendering position.
                flipInfo:  A reference to a TextureFlipInfo object.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
DrawSelectImage(
    const DrawInfo&         drawInfo,
    const Point             pt,
    const Size              size,
    const TexMap&           texMap,
    const Point             texCd,
    const TextureFlipInfo&  flipInfo
)
{
    const Rect drawRect = GetDrawRect(pt, size, drawInfo.GetInvalidateRect());
    if (drawRect.GetWidth() == 0) // Quit when there is no rendering area
    {
        return;
    }

    const int ix = flipInfo.swap ? 1: 0;
    const int iy = ix ^ 1;

    const Size texSize = texMap.GetSize();
    const s16 texMaxCdX = static_cast<s16>(texSize[ix] - 1);    // Maximum valid texture x-coordinate
    const s16 texMaxCdY = static_cast<s16>(texSize[iy] - 1);    // Y
    const s16 dirX = flipInfo.dir[ix];                          // x increment
    const s16 dirY = flipInfo.dir[iy];                          // Y
    const s16 texStartCdX = static_cast<s16>(texCd[ix] + drawRect.left * dirX); // Starting texture x-coordinate
    s16 texCrCdY          = static_cast<s16>(texCd[iy] + drawRect.top  * dirY); // Y

    Point texPt;

    #if ! defined(NTMV_M2D_USE_DIRECTTEXTURE)
        TexelPicker texelPickerMem;
        TexelPicker* pTexelPicker = TexelPicker::Create(&texelPickerMem, &texMap);
    #endif  // #if ! defined(NTMV_M2D_USE_DIRECTTEXTURE)

    for (s16 y = drawRect.top; y < drawRect.bottom; ++y, texCrCdY += dirY)
    {
        u16 *const dstLineBuf = GetLineBuf(drawInfo, static_cast<s16>(pt.y + y));

        texPt[iy] = Min(Max(s16(0), texCrCdY), texMaxCdY);

        s16 texCrCdX = texStartCdX;
        for (int x = drawRect.left; x < drawRect.right; ++x, texCrCdX += dirX)
        {
            texPt[ix] = Min(Max(s16(0), texCrCdX), texMaxCdX);

            #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                const u16 *const srcLineBuf = GetLineBuf(texMap, texPt.y);
                const u16 pixel = srcLineBuf[texPt.x];
            #else   // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)
                const u16 pixel = pTexelPicker->Get(texPt);
            #endif  // #if defined(NTMV_M2D_USE_DIRECTTEXTURE)

            if (pixel & 0x8000) // Render if the alpha bit is 1
            {
                dstLineBuf[pt.x + x] = MakeSelectedColor(pixel);
            }
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         GetOwnerFavoriteColor

  Description:  Gets the Favorite Color.

  Arguments:    None.

  Returns:      Returns the Favorite Color number (0-15).
 *---------------------------------------------------------------------------*/
u8
GetOwnerFavoriteColor()
{
    OSOwnerInfo ownerInfo;
    OS_GetOwnerInfo(&ownerInfo);
    return ownerInfo.favoriteColor;
}

/*---------------------------------------------------------------------------*
  Name:         LoadFile

  Description:  Loads a file to memory.

  Arguments:    filePath:    The path of the file to load.
                pAllocator:  A pointer to an NNSFndAllocator object.
                align:       Alignment value for the buffer to load a file.
                pFileBytes:  Pointer to a variable storing the file size (byte count).
                             Specify a NULL pointer when this is unnecessary.

  Returns:      Returns the starting address of memory allocated to load the file if it failed to be loaded.
                
                Returns a 0 if failed.
 *---------------------------------------------------------------------------*/
void*
LoadFile(
    const char*         filePath,
    NNSFndAllocator*    pAllocator,
    u32                 align,
    u32*                pFileBytes
)
{
    NNS_NULL_ASSERT(filePath);

    FSFile file;
    FS_InitFile(&file);

    if (! FS_OpenFile(&file, filePath))
    {
        NTMV_WARNING(false, "file (%s) not found", filePath);
        return 0;
    }

    const u32 fileBytes = FS_GetLength(&file);
    const u32 allocBytes = fileBytes + (align == 4 ? 0: align);
    void* buf = NNS_FndAllocFromAllocator(pAllocator, allocBytes);
    if (buf)
    {
        u32 readBytes = static_cast<u32>(FS_ReadFile(&file, RoundUp(buf, align), static_cast<s32>(fileBytes)));
        if (readBytes == fileBytes)
        {
            if (pFileBytes)
            {
                *pFileBytes = fileBytes;
            }
        }
        else
        {
            NTMV_WARNING(false, "fail to load file: %s", filePath);
            NNS_FndFreeToAllocator(pAllocator, buf);
            buf = 0;
        }
    }
    else
    {
        NTMV_WARNING(false, "cant allocate memory for file: %s", filePath);
    }

    BOOL bSuccess = FS_CloseFile(&file);
    NTMV_WARNING(bSuccess, "fail to close file: %s", filePath);

    return buf;
}

/*---------------------------------------------------------------------------*
  Name:         LoadBLZFile

  Description:  Expands a BLZ-compressed file into memory.

  Arguments:    filePath:      The path of the file to load.
                pAllocator:    A pointer to an NNSFndAllocator object.
                align:         Alignment value for the buffer where the file will be expanded.
                pExtractBytes: Pointer to a variable storing the expanded size (byte count).
                               Specify a NULL pointer when this is unnecessary.

  Returns:      Returns the starting address of memory allocated to load the file if it failed to be loaded.
                
                Returns a 0 if failed.
 *---------------------------------------------------------------------------*/
void*
LoadBLZFile(
    const char*         filePath,
    NNSFndAllocator*    pAllocator,
    u32                 align,
    u32*                pExtractBytes
)
{
    NNS_NULL_ASSERT(filePath);

    FSFile file;
    FS_InitFile(&file);

    if (! FS_OpenFile(&file, filePath))
    {
        NTMV_WARNING(false, "file (%s) not found", filePath);
        return 0;
    }

    const u32 compBytes = FS_GetLength(&file);  // Unexpanded size
    void* buf = 0;

    do
    {
        // Get the expanded size
        if (! FS_SeekFile(&file, -4, FS_SEEK_END))
        {
            NTMV_WARNING(false, "fail to seek file: %s", filePath);
            break;
        }
        
        u32 originalBottom; // // Offset to the last address of the expanded buffer
        if (sizeof(u32) != FS_ReadFile(&file, &originalBottom, sizeof(u32)))
        {
            NTMV_WARNING(false, "fail to read file: %s", filePath);
            break;
        }

        const u32 extractBytes = originalBottom + compBytes;    // Expanded size

        if (! FS_SeekFileToBegin(&file))
        {
            NTMV_WARNING(false, "fail to seek file: %s", filePath);
            break;
        }

        const u32 allocBytes = extractBytes + (align == 4 ? 0: align);
        buf = NNS_FndAllocFromAllocator(pAllocator, allocBytes);
        if (buf)
        {
            int result = -1;    // Set initial value in error state
            void *const loadPtr = RoundUp(buf, align);
            const u32 readBytes = static_cast<u32>(FS_ReadFile(&file, loadPtr, static_cast<s32>(compBytes)));
            if (readBytes == compBytes)
            {
                result = MI_SecureUncompressBLZ(loadPtr, compBytes, extractBytes);
                if (result < 0)
                {
                    NTMV_WARNING(false, "fail to UncompressBLZ: %s. - [%d]", filePath, result);
                }
                else    // Successfully expanded
                {
                    if (pExtractBytes)
                    {
                        *pExtractBytes = extractBytes;
                    }
                }
            }
            else
            {
                NTMV_WARNING(false, "fail to load file: %s", filePath);
            }

            if (result < 0)
            {
                NNS_FndFreeToAllocator(pAllocator, buf);
                buf = 0;
            }
        }
        else
        {
            NTMV_WARNING(false, "cant allocate memory for file: %s", filePath);
        }
    } while (false);

    BOOL bSuccess = FS_CloseFile(&file);
    NTMV_WARNING(bSuccess, "fail to close file: %s", filePath);

    return buf;

}

}   // namespace detail
}   // namespace m2d
}   // namespace ntmv
