#include "stdafx.h"
#pragma hdrstop

#include "VideoModeAtm640x200.h"

// ---------------------------------------------------------------------- //

CVideoModeAtm320x200::CVideoModeAtm320x200(CAtmPalette& plt, int iScaleFactor)
    : CVideoModeBase(plt, iScaleFactor)
{

}

bool CVideoModeAtm320x200::Initialize()
{
    bool bResult = CVideoModeBase::Initialize();
    m_vecPixels.resize( GetXDimension() * GetYDimension() );
    return bResult;
}


BYTE CVideoModeAtm320x200::GetPixel(int iX, int iY) const
{
    /*
    if ( !CheckBitplane(iX) )
    return 0;
    */
    if ( iX >= 0 && iX < GetXDimension() && iY >= 0 && iY < GetYDimension() )
    {
        return m_vecPixels[ iX + iY*GetXDimension() ];
    }
    return 0;
}

void CVideoModeAtm320x200::SetPixel(int iX, int iY, BYTE clr)
{
    if ( !CheckBitplane(iX) )
        return;

    if ( iX >= 0 && iX < GetXDimension() && iY >= 0 && iY < GetYDimension() )
    {
        m_vecPixels[ iX + iY*GetXDimension() ] = clr;
    }   
}

// ---------------------------------------------------------------------- //

BYTE CVideoModeAtm320x200::GetByte(int iBitplaneNum, int iX, int iY) const
{
    ASSERT( iX >=0 && iX<40 && iY >=0 && iY < 200);
    BYTE p0 = GetPixel( (iX * 4 + iBitplaneNum)*2, iY );
    BYTE p1 = GetPixel( (iX * 4 + iBitplaneNum)*2 + 1, iY );
    BYTE pix = ((p1 & 8) << 4) | ((p1 & 7) << 3) | ((p0 & 8) << 3) | (p0 & 7);
    return pix;
}

void CVideoModeAtm320x200::LookupCompressArea(int iBitplaneNum, int& iXMin, int& iXMax)
{
    iXMin = 0;
    iXMax = 39;
    while ( iXMin < iXMax )
    {
        bool bEmpty = true;
        for(int y = 0; y<199; y++)
        {
            if ( GetByte(iBitplaneNum, iXMin, y) )
            {
                bEmpty = false;
                break;
            }
        }
        if ( !bEmpty )
            break;
        ++iXMin;
    }

    while ( iXMax > iXMin )
    {
        bool bEmpty = true;
        for(int y = 0; y<199; y++)
        {
            if ( GetByte(iBitplaneNum, iXMin, y) )
            {
                bEmpty = false;
                break;
            }
        }
        if ( !bEmpty )
            break;
        --iXMax;
    }
}

void CVideoModeAtm320x200::CompressBitplane(int iBitplaneNum, std::vector<BYTE>& vecCompressed)
{
    int iMinX, iMaxX;
    LookupCompressArea(iBitplaneNum, iMinX, iMaxX);

    int iSkipIdx = vecCompressed.size();
    vecCompressed.push_back(iMinX);
    int iToDrawIdx = vecCompressed.size();
    vecCompressed.push_back(iMaxX-iMinX+1);

    int iMinYIdx = vecCompressed.size();
    vecCompressed.push_back(0);
    int iMaxYIdx = vecCompressed.size();
    vecCompressed.push_back(0);

    int iMinY = 199;
    int iMaxY = 0;
    int iLastCmdIdx = 0;

    for (int x=iMinX; x<=iMaxX; x++)
    {
        int y = 0;
        int iComprStart = vecCompressed.size();
        while (y<200)
        {
            BYTE b = GetByte(iBitplaneNum, x, y);
            if ( b )
            {
                iMinY = __min(iMinY, y);
                iMaxY = __min(iMaxY, y);
            }
            int yprev(++y);
            while (y<200 && GetByte(iBitplaneNum, x, y) == b) y++;
            int c = y-yprev;

            if ( b )
            {
                while ( c > 127 )
                {
                    vecCompressed.push_back( 0x7E );
                    vecCompressed.push_back(b);
                    c-=127;
                }
                iLastCmdIdx = vecCompressed.size();
                ASSERT( 0 == (c & 0x80) );
                vecCompressed.push_back( c );
                vecCompressed.push_back(b);
                if ( y == 200 )
                {
                    iLastCmdIdx = vecCompressed.size();
                    vecCompressed.push_back(0xFF);
                }
            } else {
                if ( y == 200 )
                {
                    iLastCmdIdx = vecCompressed.size();
                    vecCompressed.push_back(0xFF);

                } else {
                    iLastCmdIdx = vecCompressed.size();
                    if ( c > 127 )
                    {
                        vecCompressed.push_back(0xFE);
                        c-=128;
                    }
                    if ( c > 0 )
                    {
                        ASSERT( (0x80 | c) != 0xFF );
                        vecCompressed.push_back( 0x80 | c );
                    }
                }
            }
        }
        if ( iLastCmdIdx && !!(vecCompressed[iLastCmdIdx] & 0x80) && vecCompressed[iLastCmdIdx] != 0xFF )
        {
            vecCompressed.resize(iLastCmdIdx+1);
            vecCompressed[iLastCmdIdx] = 0xFF;
        }

        // проверяем валидность упаковки
        y = 0;
        BYTE bPrev = 0;
        int cPrev = -1;
        int iIdx = iComprStart;
        for (;;)
        {
            ASSERT( iIdx < vecCompressed.size() );
            int ctrl = (unsigned char)vecCompressed[iIdx++];
            if ( ctrl == 0xFF )
                break;

            if ( (ctrl+1) & 0x80 )
            {
                int c = (ctrl+1) & 0x7F;
                for (int i=0; i<c; i++, y++)
                {
                    BYTE b0 = GetByte(iBitplaneNum, x, y);
                    ASSERT( b0 == 0 );
                }
            } else {
                int c = ctrl + 1;
                BYTE b = vecCompressed[iIdx++];
                for (int i=0; i<c; i++, y++)
                {
                    BYTE b0 = GetByte(iBitplaneNum, x, y);

                    BYTE b0_1 = 0;
                    if ( y < 199)
                    {
                        b0_1 = GetByte(iBitplaneNum, x, y+1);
                    }
                    ASSERT( b == b0 );
                }
                bPrev = b;
                cPrev = c;
            }
        }
        ASSERT(iIdx == vecCompressed.size());
    }
    vecCompressed[iMinYIdx] = iMinY;
    vecCompressed[iMaxYIdx] = iMaxY;
}

void CVideoModeAtm320x200::CompressFrame(std::vector<BYTE>& vecCompressed)
{
    int iLenIdx = vecCompressed.size();
    vecCompressed.push_back(0);
    vecCompressed.push_back(0);
    CompressBitplane(0, vecCompressed);
    CompressBitplane(2, vecCompressed);
    CompressBitplane(1, vecCompressed);
    CompressBitplane(3, vecCompressed);

    int iSize = vecCompressed.size() - iLenIdx - 2;
    vecCompressed[iLenIdx] = iSize % 256;
    vecCompressed[iLenIdx+1] = iSize / 256;
}


void CVideoModeAtm320x200::CompressBitplaneChanges(
    int iBitplaneNum, 
    CVideoModeAtm320x200& PrevFrame, 
    std::vector<BYTE>& vecCompressed
    )
{
    int iMinX, iMaxX;
    LookupCompressArea(iBitplaneNum, iMinX, iMaxX);

    int iPrevMinX, iPrevMaxX;
    PrevFrame.LookupCompressArea(iBitplaneNum, iPrevMinX, iPrevMaxX);

    iMinX = __min(iMinX, iPrevMinX);
    iMaxX = __max(iMaxX, iPrevMaxX);

    vecCompressed.push_back(iMinX);
    vecCompressed.push_back(iMaxX-iMinX+1);
    ASSERT( (iMaxX-iMinX+1) <= 40 );

    int iMinYIdx = vecCompressed.size();
    vecCompressed.push_back(0);
    int iMaxYIdx = vecCompressed.size();
    vecCompressed.push_back(0);

    int iMinY = 199;
    int iMaxY = 0;

    int iLastCmdIdx = 0;
    for (int x = iMinX; x<=iMaxX; x++)
    {
        int y = 0;
        int iComprStart = vecCompressed.size();
        //TRACE("-------------------------\n");
        while (y<200)
        {
            BYTE b = GetByte(iBitplaneNum, x, y);
            BYTE bPrev = PrevFrame.GetByte(iBitplaneNum, x, y);

            if ( b == bPrev )
            {
                // сжимаем неизменную от кадра к кадру часть
                int yprev(++y);
                while (y<200 && b == GetByte(iBitplaneNum, x, y) && b == PrevFrame.GetByte(iBitplaneNum, x, y)) y++;
                int c = y-yprev;

                if ( y == 200 )
                {
                    iLastCmdIdx = vecCompressed.size();
                    vecCompressed.push_back(0xFF);
                } else {
                    iLastCmdIdx = vecCompressed.size();
                    while ( c >= 126 )
                    {
                        vecCompressed.push_back(0xFE);
                        c-=127;
                    }
                    if ( c >= 0 )
                    {
                        vecCompressed.push_back( 0x80 | c );
                    }
                }
            } else {
                iMinY = __min(iMinY, y);


                // сжимаем отличия
                // 1) определяем длину этих отличий
                int yprev(y++);
                while (y<200 && GetByte(iBitplaneNum, x, y) != PrevFrame.GetByte(iBitplaneNum, x, y)) y++;
                int c = y-yprev-1;

                iMaxY = __min(iMaxY, y+c);

                // сжимаем текущий кадр в пределах этих отличий обычным образом
                while (yprev < y)
                {
                    BYTE b = GetByte(iBitplaneNum, x, yprev);
                    int yprevprev(++yprev);

                    while (yprev<y && GetByte(iBitplaneNum, x, yprev) == b) yprev++;
                    int c = yprev-yprevprev;

                    //static int a = 0;
                    //TRACE("%d) count=%d, prev=%d, curr=%d, x=%d\n", a++, c, b, GetByte(iBitplaneNum, x, yprev-1), x);


                    while ( c >= 126 )
                    {
                        vecCompressed.push_back( 0x7E );
                        vecCompressed.push_back(b);
                        c-=127;
                    }

                    if ( c>=0 )
                    {
                        iLastCmdIdx = vecCompressed.size();
                        vecCompressed.push_back( c );
                        vecCompressed.push_back(b);
                    }
                }
                if ( y == 200)
                {
                    iLastCmdIdx = vecCompressed.size();
                    vecCompressed.push_back( 0xFF );
                }
            }
        }
        if ( iLastCmdIdx && !!(vecCompressed[iLastCmdIdx] & 0x80) && vecCompressed[iLastCmdIdx] != 0xFF )
        {
            vecCompressed.resize(iLastCmdIdx+1);
            vecCompressed[iLastCmdIdx] = 0xFF;
        }

        // проверяем валидность упаковки
        y = 0;
        BYTE bPrev = 0;
        int cPrev = -1;
        int iIdx = iComprStart;
        for (;;)
        {
            ASSERT( iIdx < vecCompressed.size() );
            int ctrl = (unsigned char)vecCompressed[iIdx++];
            if ( ctrl == 0xFF )
                break;

            if ( (ctrl+1) & 0x80 )
            {
                int c = (ctrl+1) & 0x7F;
                for (int i=0; i<c; i++, y++)
                {
                    BYTE b0 = GetByte(iBitplaneNum, x, y);
                    BYTE b1 = PrevFrame.GetByte(iBitplaneNum, x, y);
                    ASSERT( b0 == b1 );
                }
            } else {
                int c = ctrl + 1;
                BYTE b = vecCompressed[iIdx++];
                for (int i=0; i<c; i++, y++)
                {
                    BYTE b0 = GetByte(iBitplaneNum, x, y);
                    BYTE b1 = PrevFrame.GetByte(iBitplaneNum, x, y);

                    BYTE b0_1 = 0;
                    BYTE b1_1 = 0;

                    if ( y < 199)
                    {
                        b0_1 = GetByte(iBitplaneNum, x, y+1);
                        b1_1 = PrevFrame.GetByte(iBitplaneNum, x, y+1);
                    }

                    ASSERT( b == b0 && b0 != b1 );
                }
                bPrev = b;
                cPrev = c;
            }
        }
        ASSERT(iIdx == vecCompressed.size());
    }
    vecCompressed[iMinYIdx] = iMinY;
    vecCompressed[iMaxYIdx] = iMaxY;
}

void CVideoModeAtm320x200::CompressChanges(CVideoModeAtm320x200& PrevFrame, std::vector<BYTE>& vecCompressed)
{
    int iLenIdx = vecCompressed.size();
    vecCompressed.push_back(0);
    vecCompressed.push_back(0);
    CompressBitplaneChanges(0, PrevFrame, vecCompressed);
    CompressBitplaneChanges(2, PrevFrame, vecCompressed);
    CompressBitplaneChanges(1, PrevFrame, vecCompressed);
    CompressBitplaneChanges(3, PrevFrame, vecCompressed);

    int iSize = vecCompressed.size() - iLenIdx - 2;
    vecCompressed[iLenIdx] = iSize % 256;
    vecCompressed[iLenIdx+1] = iSize / 256;
}

void CVideoModeAtm320x200::SetByte(int iBitplaneNum, int iX, int iY, BYTE pix)
{
    ASSERT( iX >=0 && iX<40 && iY >=0 && iY < 200);
    BYTE p0 = (pix & 7)  | ((pix>>4) & 8);
    BYTE p1 = ((pix >> 3) & 7) | ((pix >> 3) & 8);
    SetPixel( (iX * 4 + iBitplaneNum)*2, iY, p0 );
    SetPixel( (iX * 4 + iBitplaneNum)*2 + 1, iY, p1 );
}

int CVideoModeAtm320x200::UncompressBitplane(int iBitplaneNum, PBYTE pb)
{
    PBYTE prev(pb);
    int x = (unsigned char)*pb++;
    int cnt = (unsigned char)*pb++;
    int ymin = (unsigned char)*pb++;
    int ymax = (unsigned char)*pb++;

    PBYTE pbBegin(pb);

    while(cnt > 0)
    {
        int y = 0;
        for (;;)
        {
            int ctrl = (unsigned char)*pb++;
            if ( ctrl == 0xFF )
                break;

            if ( (ctrl+1) & 0x80 )
            {
                //y += (ctrl & 0x7F) + 1;
                y += (ctrl+1) & 0x7F;
            } else {
                int c = ctrl + 1;
                BYTE b = *pb++;
                for (int i=0; i<c; i++, y++)
                {
                    SetByte(iBitplaneNum, x, y, b);
                }
            }
        }
        --cnt;
        x++;
    }
    return pb - prev;
}


int CVideoModeAtm320x200::UncompressFrame(PBYTE pb)
{
    int len, unpacked, total = 0;    

    len = *(short*)pb;
    pb += 2;
    unpacked = UncompressBitplane(0, pb);
    pb += unpacked;
    total += unpacked;

    unpacked = UncompressBitplane(2, pb);
    pb += unpacked;
    total += unpacked;

    unpacked = UncompressBitplane(1, pb);
    pb += unpacked;
    total += unpacked;

    unpacked = UncompressBitplane(3, pb);
    pb += unpacked;
    total += unpacked;

    ASSERT( total == len );

    return total;
}


