/*
    Project  : H.264 encoder test    
    Module   : Intra coding related functions

    Author   : Marko 'Fador' Viitanen

    Date     : 30/06/2009
    Modified : 01/07/2009

*/

#include <stdio.h>
#include "common.h"
#include "psnr.h"
#include "intra.h"


//Macro for result comparing
#define COMPARE(MODE) \
    tempsad=SAD(cur4x4L, pred4x4L,(uint32)4,(uint32)4); \
    if(tempsad<bestcost) \
    {\
        bestcost=tempsad;\
        bestIntra4x4PredMode=MODE;\
        for(temp_x=0;temp_x<4;temp_x++) \
        {\
            for(temp_y=0;temp_y<4;temp_y++)\
            {\
                residual[temp_x+temp_y*4]=(cur4x4L[temp_x+temp_y*4]);\
                residual[temp_x+temp_y*4]-=(pred4x4L[temp_x+temp_y*4]);\
            }\
        }\
    }


#define RETURNPRED() \
        for(temp_x=0;temp_x<4;temp_x++) \
        {\
            for(temp_y=0;temp_y<4;temp_y++)\
            {\
                residual[temp_x+temp_y*4]=pred4x4L[temp_x+temp_y*4];\
            }\
        }\
        return 1;



#define P(X,Y) p[(X)+1+((Y)+1)*9]


typedef uint8 (modefunc)(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail);

//Recommendation page 126
//Mode -1 = encode all and select best
sint8 intrapred_4x4(uint8 *frame, sint32 *residual, uint32 x, uint32 y, uint32 xoff, uint32 yoff, uint32 *cost, sint8 mode)
{
    
    uint32 temp_x,temp_y,tempsad;
    uint32 bestcost=0xffffffff;
    sint8 bestIntra4x4PredMode=-1;
    uint8 Intra4x4PredMode;    

    //Extended sub-macroblock
    uint8 p[9*5];

    uint8 pred4x4L[4*4];
    uint8 cur4x4L[4*4];    

    //Different prediction mode functions here
    modefunc *funcs[9]={&intraPred4x4Mode0,&intraPred4x4Mode1,&intraPred4x4Mode2,
                        &intraPred4x4Mode3,&intraPred4x4Mode4,&intraPred4x4Mode5,
                        &intraPred4x4Mode6,&intraPred4x4Mode7,&intraPred4x4Mode8};

    //Fill the availability array
    uint8 pred4x4L_avail[9*5];
    //Fill with ones
    memset(pred4x4L_avail,1,9*5);

    //First column not available at xoff==0
    if(xoff==0)
    {
        pred4x4L_avail[0]=pred4x4L_avail[9]=pred4x4L_avail[18]=pred4x4L_avail[28]=pred4x4L_avail[36]=0;
    }
    else
    {
        temp_x=0;
        //Fill I,J,K,L
        for(temp_y=1;temp_y<5;temp_y++)
            p[temp_y*9+temp_x]=frame[xoff+(temp_x-1)+(yoff+(temp_y-1))*x];
    }

    //First row not available on yoff==0
    if(yoff==0)
    {
         pred4x4L_avail[0]=pred4x4L_avail[1]=pred4x4L_avail[2]=pred4x4L_avail[3]=
             pred4x4L_avail[4]=pred4x4L_avail[5]=pred4x4L_avail[6]=pred4x4L_avail[7]=pred4x4L_avail[8]=0;
    }
    else
    {
        temp_y=0;
        //Fill (M),A,B,C & D
        for(temp_x=(pred4x4L_avail[0]?0:1);temp_x<5;temp_x++)
            p[temp_y*9+temp_x]=frame[xoff+(temp_x-1)+(yoff+(temp_y-1))*x];
    }


    //E-H not available at right side
    if((xoff%16 == 15 && yoff%16!=0) || (x-xoff)==4)
    {        
        pred4x4L_avail[5]=pred4x4L_avail[6]=pred4x4L_avail[7]=pred4x4L_avail[8]=0;
    }
    else
    {
        temp_y=0;
        //Fill E,F,G & H
        for(temp_x=5;temp_x<9;temp_x++)
            p[temp_y*9+temp_x]=frame[xoff+(temp_x-1)+(yoff+(temp_y-1))*x];
    }
    


    //Current MB
    for(temp_x=0;temp_x<4;temp_x++)
            for(temp_y=0;temp_y<4;temp_y++)
                cur4x4L[temp_x+temp_y*4]=frame[xoff+temp_x+(yoff+temp_y)*x];

    //If we return only one prediction result
    if(mode!=-1)
    {
        (*funcs[mode])(pred4x4L,p,pred4x4L_avail);
        RETURNPRED();       
    }
    else
    {
        //Loop through all prediction modes calling the functions
        //and if prediction is executed, compare the SAD of the results to
        //current best cost
        for(Intra4x4PredMode=0;Intra4x4PredMode<8;Intra4x4PredMode++)
        {
            if((*funcs[Intra4x4PredMode])(pred4x4L,p,pred4x4L_avail))
            {
                COMPARE(Intra4x4PredMode);
            }
        }
    }

    *cost=bestcost;

    return bestIntra4x4PredMode;
}

//MODE0 - Intra_4x4_Vertical
// This mode shall be used only when the samples p[ x, -1 ] with x = 0..3 are marked as 
//"available for Intra_4x4prediction".
uint8 intraPred4x4Mode0(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail)
{
    uint32 temp_x;
    uint32 temp_y;

    if(pred4x4L_avail[1] //&& pred4x4L_avail[2] &&
        //pred4x4L_avail[3] &&pred4x4L_avail[4]
        )
    {
        for(temp_x=0;temp_x<4;temp_x++)
        {
            for(temp_y=0;temp_y<4;temp_y++)
            {
                pred4x4L[ temp_x+temp_y*4 ] = P(temp_x, -1);
            }
        }      
        return 1;
    }
    return 0;
}

//MODE1 - Intra_4x4_Horizontal
//This mode shall be used only when the samples p[ -1, y ], 
//with y = 0..3 are marked as "available for Intra_4x4 prediction". 
uint8 intraPred4x4Mode1(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail)
{
    uint32 temp_x;
    uint32 temp_y;
   
    if(pred4x4L_avail[9] // && pred4x4L_avail[18] &&
       //pred4x4L_avail[27] && pred4x4L_avail[36]
        )
    {
        for(temp_x=0;temp_x<4;temp_x++)
            for(temp_y=0;temp_y<4;temp_y++)
                pred4x4L[ temp_x+temp_y*4 ] = P(-1,temp_y);
        return 1;
    }
    return 0;
}

//MODE2 - Intra_4x4_DC
//If all samples p[ x, -1 ], with x = 0..3 and p[ -1, y ], 
//with y = 0..3 are marked as "available for Intra_4x4 prediction"
uint8 intraPred4x4Mode2(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail)
{
    uint32 temp_x;
    uint32 temp_y;

    if(pred4x4L_avail[9] && //pred4x4L_avail[18] &&
        //pred4x4L_avail[27] &&pred4x4L_avail[36] &&
        pred4x4L_avail[1] //&& pred4x4L_avail[2] &&
        //pred4x4L_avail[3] &&pred4x4L_avail[4]
        )
    {
        for(temp_x=0;temp_x<4;temp_x++)
            for(temp_y=0;temp_y<4;temp_y++)
                pred4x4L[ temp_x+temp_y*4 ] = (P(0,-1) + P(1,-1) + P(2,-1) + 
                                               P(3,-1) + P(-1,0) + P(-1,1) +
                                               P(-1,2) + P(-1,3) + 4) >> 3;
        return 1;
    }
    //- Otherwise, if any samples p[ x, -1 ], with x = 0..3 are marked as 
    //"not available for Intra_4x4 prediction" and all
    //samples p[ -1, y ], with y = 0..3 are marked as "available for Intra_4x4 prediction"
    else if(pred4x4L_avail[9]// && pred4x4L_avail[18] &&
            //pred4x4L_avail[27] &&pred4x4L_avail[36]
            )
    {
        for(temp_y=0;temp_y<4;temp_y++)
            for(temp_x=0;temp_x<4;temp_x++)
                pred4x4L[ temp_x+temp_y*4 ] = (P(-1,0)+P(-1,1)+P(-1,2)+P(-1,3)+2)>>2;
        return 1;
    }
    //- Otherwise, if any samples p[ -1, y ], with y = 0..3 are marked as 
    //"not available for Intra_4x4 prediction" and all
    //samples p[ x, -1 ], with x = 0 .. 3 are marked as "available for Intra_4x4 prediction"
    else if(pred4x4L_avail[1] //&& pred4x4L_avail[2] &&
        //pred4x4L_avail[3] &&pred4x4L_avail[4]
            )
    {
        for(temp_y=0;temp_y<4;temp_y++)
            for(temp_x=0;temp_x<4;temp_x++)
                pred4x4L[ temp_x+temp_y*4 ] = (P(0,-1)+P(1,-1)+P(2,-1)+P(3,-1)+2)>>2; 
        return 1;
    }
    return 0;
}

//MODE3 - Intra_4x4_Diagonal_Down_Left
//This mode shall be used only when the samples p[ x, -1 ] with x = 0..7 are marked as 
//"available for Intra_4x4 prediction".
uint8 intraPred4x4Mode3(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail)
{
    uint32 temp_x;
    uint32 temp_y;
    if(pred4x4L_avail[1] && //pred4x4L_avail[2] &&
       //pred4x4L_avail[3] && pred4x4L_avail[4] &&
       pred4x4L_avail[5] //&& pred4x4L_avail[6] &&
       //pred4x4L_avail[7] && pred4x4L_avail[8]
        )
    {
        for(temp_y=0;temp_y<4;temp_y++)
        {
            for(temp_x=0;temp_x<4;temp_x++)
            {
                if(temp_y==3&&temp_x==3)
                {
                    pred4x4L[temp_x+ temp_y*4] = ( P(6,-1) + 3 * P(7,-1) + 2 ) >> 2;
                }
                else
                {
                    pred4x4L[temp_x+ temp_y*4] = ( P((temp_x+temp_y),-1) + 2 * P(temp_x+temp_y+1,-1) + P(temp_x+temp_y+2,-1)+2) >> 2;
                }
            }
        }
        return 1;
    }
    return 0;
}

//MODE4 - Intra_4x4_Diagonal_Down_Right
//This mode shall be used only when the samples 
//p[ x, -1 ] with x = 0..3 and p[ -1, y ] with y = -1..3 are marked as
//"available for Intra_4x4 prediction
uint8 intraPred4x4Mode4(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail)
{
    uint32 temp_x;
    uint32 temp_y;
    if(pred4x4L_avail[1] &&// pred4x4L_avail[2] &&
       //pred4x4L_avail[3] && pred4x4L_avail[4] &&
       pred4x4L_avail[0] //&& pred4x4L_avail[9] &&
       //pred4x4L_avail[18]&& pred4x4L_avail[27]&&
       //pred4x4L_avail[36]
       )
    {

        for(temp_x=0;temp_x<4;temp_x++)
        {
            for(temp_y=0;temp_y<4;temp_y++)
            {
                if(temp_x>temp_y)
                    pred4x4L[temp_x+temp_y*4] = ( P(temp_x-temp_y-2,-1) + 2 * P(temp_x-temp_y-1,-1) + P(temp_x-temp_y,-1) + 2 ) >> 2;
                else if(temp_x<temp_y)
                    pred4x4L[temp_x+temp_y*4] = ( P(-1,temp_y-temp_x-2) + 2 * P(-1,temp_y-temp_x-1) + P(-1,temp_y-temp_x) + 2 ) >> 2;
                else
                    pred4x4L[temp_x+temp_y*4] =( P(0,-1) + 2 * P(-1,-1) + P(-1,0) + 2 ) >> 2;
            }
        }
        return 1;
    }
    return 0;
}

//MODE5 - Intra_4x4_Vertical_Right
//This mode shall be used only when the samples 
//p[ x, -1 ] with x = 0..3 and p[ -1, y ] with y = -1..3 are marked as
//"available for Intra_4x4 prediction".
uint8 intraPred4x4Mode5(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail)
{
    uint32 temp_x;
    uint32 temp_y;
    if(pred4x4L_avail[1] &&// pred4x4L_avail[2] &&
       //pred4x4L_avail[3] && pred4x4L_avail[4] &&
       pred4x4L_avail[0] //&& pred4x4L_avail[9] &&
       //pred4x4L_avail[18] && pred4x4L_avail[27]&&
       //pred4x4L_avail[36]
       )
    {

        #define zVR (sint32)(2*temp_x-temp_y)
        for(temp_x=0;temp_x<4;temp_x++)
        {
            for(temp_y=0;temp_y<4;temp_y++)
            {
                if(zVR>=0)
                {
                    if(zVR%2==0) //0,2,4,6
                        pred4x4L[temp_x+temp_y*4] = ( P(temp_x-( temp_y>>1)-1,-1) + P(temp_x-(temp_y>>1),-1) + 1)>> 1;
                    else //if(zVR%2==1) //1,3,5
                        pred4x4L[temp_x+temp_y*4] = ( P(temp_x-(temp_y>>1)-2,-1) + 2*P(temp_x-(temp_y>>1)-1,-1) + P(temp_x-(temp_y>>1),-1) + 2)>>2;
                }
                else
                {
                    if(zVR==-1)
                        pred4x4L[temp_x+temp_y*4] =(P(-1,0)+2*P(-1,-1)+P(0,-1)+2)>>2;
                    else
                        pred4x4L[temp_x+temp_y*4] = (P(-1,temp_y-1)+2*P(-1,temp_y-2)+P(-1,temp_y-3)+2)>>2;
                }
            }
        }
        return 1;
    }
    return 0;
}

//MODE6 - Intra_4x4_Horizontal_Down
//This mode shall be used only when the samples p[ x, -1 ] 
//with x = 0..3 and p[ -1, y ] with y = -1..3 are marked as
//"available for Intra_4x4 prediction".
uint8 intraPred4x4Mode6(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail)
{
    uint32 temp_x;
    uint32 temp_y;
    if(pred4x4L_avail[1] && //pred4x4L_avail[2] &&
       //pred4x4L_avail[3] && pred4x4L_avail[4] &&
       pred4x4L_avail[0] //&& pred4x4L_avail[9] &&
       //pred4x4L_avail[18]&& pred4x4L_avail[27]&&
       //pred4x4L_avail[36]
        )
    {

        #define zHD (sint32)(2*temp_y-temp_x)
        for(temp_x=0;temp_x<4;temp_x++)
        {
            for(temp_y=0;temp_y<4;temp_y++)
            {
                if(zHD>=0)
                {
                    if(zHD%2==0) //0,2,4,6
                        pred4x4L[temp_x+temp_y*4] = (P(-1,temp_y-(temp_x>>1)-1) + P(-1,temp_y-(temp_x>>1)) + 1)>>1; //(8-61)
                    else //1,3,5
                        pred4x4L[temp_x+temp_y*4] = (P(-1,temp_y-(temp_x>>1)-2) + 2*P(-1,temp_y-(temp_x>>1)-1) + P(-1,temp_y-(temp_x>>1)) + 2)>> 2; //(8-62)
                }
                else
                {
                    if(zHD==-1)
                        pred4x4L[temp_x+temp_y*4] = (P(-1,0) + 2*P(-1,-1) + P(0,-1) + 2)>>2; //(8-63)
                    else
                        pred4x4L[temp_x+temp_y*4] = (P(temp_x-1,-1) + 2*P(temp_x-2,-1) + P(temp_x-3,-1) + 2)>> 2; //(8-64)
                }
            }
        }
        return 1;
    }
    return 0;
}

//MODE7 - Intra_4x4_Vertical_Left
//This mode shall be used only when the samples p[ x, -1 ] 
//with x = 0..7 are marked as "available for Intra_4x4 prediction".
uint8 intraPred4x4Mode7(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail)
{
    uint32 temp_x;
    uint32 temp_y;

    if(pred4x4L_avail[1] &&// pred4x4L_avail[2] &&
       pred4x4L_avail[3] //&& pred4x4L_avail[4] &&
       //pred4x4L_avail[5] && pred4x4L_avail[6] &&
       //pred4x4L_avail[7] && pred4x4L_avail[8]
       )
    {   
        for(temp_x=0;temp_x<4;temp_x++)
        {            
            for(temp_y=0;temp_y<4;temp_y++)
            {
                if(temp_y==0 && temp_y==2)
                {
                    pred4x4L[temp_x+temp_y*4] = (P(temp_x+(temp_y>>1),-1) + P(temp_x+(temp_y>>1)+1,-1) + 1) >> 1; //(8-66)
                }
                else
                {
                     pred4x4L[temp_x+temp_y*4] = (P(temp_x+(temp_y>>1),-1) + 2*P(temp_x+(temp_y>>1)+1,-1) + P(temp_x+(temp_y>>1)+2,-1) + 2 )>> 2; //(8-67)
                }

            }
        }
        return 1;
    }
    return 0;
}


    //MODE8 - Intra_4x4_Horizontal_Up
    //This mode shall be used only when the samples p[ -1, y ] 
    //with y = 0..3 are marked as "available for Intra_4x4 prediction".
uint8 intraPred4x4Mode8(uint8 *pred4x4L, uint8 *p, uint8 *pred4x4L_avail)
{
    uint32 temp_x;
    uint32 temp_y;
     if(pred4x4L_avail[1] //&& pred4x4L_avail[2] &&
        //pred4x4L_avail[3] && pred4x4L_avail[4]
        )
    {

        #define zHU (sint32)(temp_x+2*temp_y)
        for(temp_x=0;temp_x<4;temp_x++)
        {            
            for(temp_y=0;temp_y<4;temp_y++)
            {
                if(zHU<5&&zHU%2==0)
                {
                    pred4x4L[temp_x+temp_y*4] = (P(-1,temp_y+(temp_x>>1)) + P(-1,temp_y+(temp_x>>1)+1) + 1) >> 1;// (8-68)
                }
                else if(zHU<5&&zHU%2!=0)
                {
                     pred4x4L[temp_x+temp_y*4] = (P(-1,temp_y+(temp_x>>1)) + 2*P(-1,temp_y+(temp_x>>1)+1) + P(-1,temp_y+(temp_x>>1)+2) +2) >> 2;//(8-69)
                }
                else if(zHU==5)
                {
                    pred4x4L[temp_x+temp_y*4] = (P(-1,2) + 3*P(-1,3) + 2 ) >> 2;// (8-70)
                }
                else
                {
                    pred4x4L[temp_x+temp_y*4] = P(-1,3);// (8-71)
                }
            }
        }
        return 1;
    }
    return 0;
}
