//*@@@+++@@@@******************************************************************
//
// Copyright © Microsoft Corp.
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// • Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// • Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//*@@@---@@@@******************************************************************
#ifndef ANSI
#define _CRT_SECURE_NO_WARNINGS
#endif ANSI

#include <stdlib.h>

#include <JXRTest.h>


//================================================================
// PKImageEncode_PNM helpers
//================================================================
ERR WritePNMHeader(PKImageEncode* pIE)
{
    ERR err = WMP_errSuccess;

    PKPixelInfo PI;
    struct WMPStream* pS = pIE->pStream;
    U8 buf[64] = {0};
    int cb = 0;

    char szSig[2];
    U32 uMaxVal = 0;

    PI.pGUIDPixFmt = &pIE->guidPixFormat;
    PixelFormatLookup(&PI, LOOKUP_FORWARD);

    if (IsEqualGUID(&GUID_PKPixelFormatBlackWhite, PI.pGUIDPixFmt))
    {
        szSig[0] = 'P', szSig[1] = '5';
        uMaxVal = 1;
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat8bppGray, PI.pGUIDPixFmt))
    {
        szSig[0] = 'P', szSig[1] = '5';
        uMaxVal = 255;
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat24bppRGB, PI.pGUIDPixFmt))
    {
        szSig[0] = 'P', szSig[1] = '6';
        uMaxVal = 255;
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat48bppRGB, PI.pGUIDPixFmt))
    {
        szSig[0] = 'P', szSig[1] = '6';
        uMaxVal = 65535;
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat16bppGray, PI.pGUIDPixFmt))
    {
        szSig[0] = 'P', szSig[1] = '6';
        uMaxVal = 65535;
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat96bppRGBFloat, PI.pGUIDPixFmt))
    {
        szSig[0] = 'P', szSig[1] = 'F';
    }
    else
        Call(WMP_errUnsupportedFormat);

    if('P' == szSig[0] && 'F' == szSig[1])
        cb = sprintf((char *) buf, "%c%c\n%u\n%u\n%s\n",
            szSig[0], szSig[1], (int)pIE->uWidth, (int)pIE->uHeight, "-1.0000");
    else
        cb = sprintf((char *) buf, "%c%c\n%u %u\n%u\n",
            szSig[0], szSig[1], (int)pIE->uWidth, (int)pIE->uHeight, (int)uMaxVal);

    assert(cb < sizeof2(buf));
    Call(pS->Write(pS, buf, cb));

    Call(pS->GetPos(pS, &pIE->offPixel));
    pIE->cbPixel = ((PI.cbitUnit + 7) >> 3);// ->cbPixel / pPI->cbPixelDenom;
    pIE->fHeaderDone = !FALSE;

Cleanup:
    return err;
}

//================================================================
// PKImageEncode_PNM
//================================================================
ERR PKImageEncode_WritePixels_PNM(
    PKImageEncode* pIE,
    U32 cLine,
    U8* pbPixel,
    U32 cbStride)
{
    ERR err = WMP_errSuccess;

    struct WMPStream* pS = pIE->pStream;
    size_t cbLine = 0;
    size_t offPos = 0;
    size_t i = 0;

    // header
    if (!pIE->fHeaderDone)
    {
        Call(WritePNMHeader(pIE));
    }

    // body
    cbLine = pIE->cbPixel * pIE->uWidth;
    FailIf(cbStride < cbLine, WMP_errInvalidParameter);

    offPos = pIE->offPixel + cbLine * pIE->idxCurrentLine;
    Call(pS->SetPos(pS, offPos));

    for (i = 0; i < cLine; ++i)
    {
        Call(pS->Write(pS, pbPixel + cbStride * i, cbLine));
    }
    pIE->idxCurrentLine += cLine;

Cleanup:
    return err;
}

ERR PKImageEncode_Create_PNM(
    PKImageEncode** ppIE)
{
    ERR err = WMP_errSuccess;
    PKImageEncode* pIE = NULL;

    Call(PKImageEncode_Create(ppIE));

    pIE = *ppIE;
    pIE->WritePixels = PKImageEncode_WritePixels_PNM;

Cleanup:
    return err;
}


//================================================================
// PKImageDecode_PNM helpers
//================================================================
ERR GetLineSkipPound(struct WMPStream* pWS, U8* pb, size_t cb)
{
    ERR err = WMP_errSuccess;
    U8 *pb1;
    size_t cb1;

    do
    {
        pb1 = pb;
        cb1 = cb;

        do {
            Call(pWS->Read(pWS, pb1, 1));
            cb1--;
            pb1++;
        }
        while (cb1 > 0 && pb1[-1] != '\n');

        //Call(pWS->GetLine(pWS, pb, cb));
    } while('#' == pb[0]);

Cleanup:
    return err;
}

ERR ParsePNMHeader(
    PKTestDecode* pID,
    struct WMPStream* pWS)
{
    ERR err = WMP_errSuccess;

    U8 line[128] = {0};
    size_t idxChannel = 0, idxBitDepth = 0;
    unsigned int width = 0, height = 0, maxval = 0;

    static const PKPixelFormatGUID* pixFormat[2][2] =
    {
        {&GUID_PKPixelFormat8bppGray, &GUID_PKPixelFormat16bppGray,},
        {&GUID_PKPixelFormat24bppRGB, &GUID_PKPixelFormat48bppRGB,},
    };

    //================================
    Call(GetLineSkipPound(pWS, line, sizeof2(line)));
    if (line == (U8 *) strstr((char *) line, "P5"))
    {
        idxChannel = 0;
        Call(GetLineSkipPound(pWS, line, sizeof2(line)));
        FailIf(2 != sscanf((char *) line, "%u %u", &width, &height), WMP_errUnsupportedFormat);
    }
    else if(line == (U8 *) strstr((char *) line, "P6"))
    {
        idxChannel = 1;
        Call(GetLineSkipPound(pWS, line, sizeof2(line)));
        FailIf(2 != sscanf((char *) line, "%u %u", &width, &height), WMP_errUnsupportedFormat);
    } 
    else if(line == (U8 *) strstr((char *) line, "PF")) 
    {
        idxChannel = 2;
        Call(GetLineSkipPound(pWS, line, sizeof2(line)));
        FailIf(1 != sscanf((char *) line, "%u", &width), WMP_errUnsupportedFormat);
        Call(GetLineSkipPound(pWS, line, sizeof2(line)));
        FailIf(1 != sscanf((char *) line, "%u", &height), WMP_errUnsupportedFormat);
    } 
    else
    {
        Call(WMP_errUnsupportedFormat);
    }

    //================================
//    Call(GetLineSkipPound(pWS, line, sizeof2(line)));
//    FailIf(2 != sscanf(line, "%u %u", &width, &height), WMP_errUnsupportedFormat);

    FailIf(0 == width || 0 == height, WMP_errUnsupportedFormat);

    pID->uWidth = (U32)width;
    pID->uHeight = (U32)height;

    //================================
    Call(GetLineSkipPound(pWS, line, sizeof2(line)));

    FailIf(1 != sscanf((char *) line, "%u", &maxval), WMP_errUnsupportedFormat);

    if (2==idxChannel)
    {
        FailIf(maxval != -1, WMP_errUnsupportedFormat);
        pID->guidPixFormat = GUID_PKPixelFormat96bppRGBFloat;
    }
    else
    {
        FailIf(maxval < 1 || 65535 < maxval, WMP_errUnsupportedFormat);
        idxBitDepth = 255 < maxval;
        pID->guidPixFormat = *pixFormat[idxChannel][idxBitDepth];
    }

    Call(pWS->GetPos(pWS, &pID->EXT.PNM.offPixel));

Cleanup:
    return err;
}


//================================================================
// PKImageDecode_PNM
//================================================================
ERR PKImageDecode_Initialize_PNM(
    PKTestDecode* pID,
    struct WMPStream* pWS)
{
    ERR err = WMP_errSuccess;

    Call(PKTestDecode_Initialize(pID, pWS));
    Call(ParsePNMHeader(pID, pWS));

Cleanup:
    return err;
}


ERR PKImageDecode_Copy_PNM(
    PKTestDecode* pID,
    const PKRect* pRect,
    U8* pb,
    U32 cbStride)
{
    ERR err = WMP_errSuccess;

    struct WMPStream* pS = pID->pStream;
    PKPixelInfo PI;
    size_t cbLineS = 0;
    size_t cbLineM = 0;
    I32 i = 0;

    PI.pGUIDPixFmt = &pID->guidPixFormat;
    PixelFormatLookup(&PI, LOOKUP_FORWARD);

    cbLineS = (BD_1 == PI.bdBitDepth ? ((PI.cbitUnit * pID->uWidth + 7) >> 3) : (((PI.cbitUnit + 7) >> 3) * pID->uWidth)); 
    cbLineM = (BD_1 == PI.bdBitDepth ? ((PI.cbitUnit * pRect->Width + 7) >> 3) : (((PI.cbitUnit + 7) >> 3) * pRect->Width)); 
    FailIf(cbStride < cbLineM, WMP_errInvalidParameter);

    for (i = 0; i < pRect->Height; ++i)
    {
        size_t offLine = (BD_1 == PI.bdBitDepth ? ((PI.cbitUnit * pRect->X + 7) >> 3) : (((PI.cbitUnit + 7) >> 3) * pRect->X)); 
        size_t offS = cbLineS * (pRect->Y + i) + offLine;
        size_t offM = cbStride * i + offLine;

        Call(pS->SetPos(pS, pID->EXT.PNM.offPixel + offS));
        Call(pS->Read(pS, pb + offM, cbLineM));
    }

Cleanup:
    return err;
}

ERR PKImageDecode_Create_PNM(
    PKTestDecode** ppID)
{
    ERR err = WMP_errSuccess;
    PKTestDecode* pID = NULL;

    Call(PKTestDecode_Create(ppID));

    pID = *ppID;
    pID->Initialize = PKImageDecode_Initialize_PNM;
    pID->Copy = PKImageDecode_Copy_PNM;

Cleanup:
    return err;
}