#include "version.h"

#ifdef USE_SHADING
#include "wl_def.h"
#include "wl_shade.h"

typedef struct {
    uint8_t destRed, destGreen, destBlue;   // values between 0 and 255
    uint8_t fogStrength;
} shadedef_t;

shadedef_t shadeDefs[] = {
    {   0,   0,   0, LSHADE_NOSHADING },
    {   0,   0,   0, LSHADE_NORMAL },
    {   0,   0,   0, 32},
    {   0,   0,   0, LSHADE_FOG },
    {  40,  40,  40, LSHADE_NORMAL },
    {  60,  60,  60, LSHADE_FOG }
};

uint8_t shadetable[SHADE_COUNT][256];
int LSHADE_flag;
#ifdef USE_ADVSHADES
double lightflash = 0;
#endif 

#ifdef USE_FEATUREFLAGS

// The lower 8-bit of the upper left tile of every map determine
// the used shading definition of shadeDefs.
static inline int GetShadeDefID()
{
    int shadeID = ffDataTopLeft & 0x00ff;
    assert(shadeID >= 0 && shadeID < lengthof(shadeDefs));
    return shadeID;
}

#else

static int GetShadeDefID()
{
    int shadeID;
    switch(gamestate.episode * 10 + mapon)
    {   default: shadeID = 2; break;
    }
    assert(shadeID >= 0 && shadeID < lengthof(shadeDefs));
    return shadeID;
}

#endif


// Returns the palette index of the nearest matching color of the
// given RGB color in given palette
byte GetColor(byte red, byte green, byte blue, SDL_Color *palette)
{
    byte mincol = 0;
    double mindist = 200000.F, curdist, DRed, DGreen, DBlue;

    SDL_Color *palPtr = palette;

    for(int col = 0; col < 256; col++, palPtr++)
    {
        DRed   = (double) (red   - palPtr->r);
        DGreen = (double) (green - palPtr->g);
        DBlue  = (double) (blue  - palPtr->b);
        curdist = DRed * DRed + DGreen * DGreen + DBlue * DBlue;
        if(curdist < mindist)
        {
            mindist = curdist;
            mincol = (byte) col;
        }
    }
    return mincol;
}
// Merges two colours based on their strength and returns the closest palette colour
// If only we had 32-bit colour... *scratches chin*
byte MergeColour(byte col1, byte col2, float c1s, float c2s)
{
    SDL_Color *pal = gamepal;
    byte r, g, b;

    r = (byte) ((((pal + col1)->r)*c1s + ((pal + col2)->r)*c2s) / (c1s + c2s));
    g = (byte) ((((pal + col1)->g)*c1s + ((pal + col2)->g)*c2s) / (c1s + c2s));
    b = (byte) ((((pal + col1)->b)*c1s + ((pal + col2)->b)*c2s) / (c1s + c2s));

    return GetColor(r, g, b, gamepal);
}
// Fade all colors in 32 steps down to the destination-RGB
// (use gray for fogging, black for standard shading)
void GenerateShadeTable(byte destRed, byte destGreen, byte destBlue,
                        SDL_Color *palette, int fog)
{
    double curRed, curGreen, curBlue, redStep, greenStep, blueStep;
    SDL_Color *palPtr = palette;

    // Set the fog-flag
    LSHADE_flag=fog;

    // Color loop
    for(int i = 0; i < 256; i++, palPtr++)
    {
        // Get original palette color
        curRed   = palPtr->r;
        curGreen = palPtr->g;
        curBlue  = palPtr->b;

        // Calculate increment per step
        redStep   = ((double) destRed   - curRed)   / (SHADE_COUNT + 8);
        greenStep = ((double) destGreen - curGreen) / (SHADE_COUNT + 8);
        blueStep  = ((double) destBlue  - curBlue)  / (SHADE_COUNT + 8);

        // Calc color for each shade of the current color
        for (int shade = 0; shade < SHADE_COUNT; shade++)
        {
            shadetable[shade][i] = GetColor((byte) curRed, (byte) curGreen, (byte) curBlue, palette);

            // Inc to next shade
            curRed   += redStep;
            curGreen += greenStep;
            curBlue  += blueStep;
        }
    }
}

void NoShading()
{
    for(int shade = 0; shade < SHADE_COUNT; shade++)
        for(int i = 0; i < 256; i++)
            shadetable[shade][i] = i;
}
#ifdef USE_ADVSHADES
void InitLevelShadeMap(void);
#endif
void InitLevelShadeTable()
{
    shadedef_t *shadeDef = &shadeDefs[GetShadeDefID()];
    if(shadeDef->fogStrength == LSHADE_NOSHADING)
        NoShading();
    else
        GenerateShadeTable(shadeDef->destRed, shadeDef->destGreen, shadeDef->destBlue, gamepal, shadeDef->fogStrength);
#ifdef USE_ADVSHADES
  //Fill the lightmap
    InitLevelShadeMap();
#endif
}

//
//Get the shade to use, based on the_fish's code:
#ifdef USE_ADVSHADES
double inline GetLightFrag(int x, int y, int tx, int ty, byte sx, byte sy);
double inline FlashlightIntensity(int scale, double radius);
int GetShade(int scale,int dx,int dy,int tx1, int ty1, int tx2, int ty2, byte sx, byte sy)
{   int16_t shade;              
  //Count light fraction...   
    double pixradius = (dx*dx + dy*dy*1.4)/viewwidth;
    double lightfrag = GetLightFrag(tx1,ty1,tx2,ty2,sx,sy)/256;
  //...and limit it: 
    if(lightfrag > 1) lightfrag = 1;
    if(lightfrag < 0) lightfrag = 0; 
    
    if(gamestate.flashlight && pixradius < 12)
       shade = (int)(SHADE_COUNT*(FlashlightIntensity(scale, pixradius)+lightfrag+lightflash));
    else
       shade = (int)(SHADE_COUNT*(lightfrag+lightflash));
#else    
int GetShade(int scale)
{
    int shade = (scale >> 1) / (((viewwidth * 3) >> 8) + 1 + LSHADE_flag);  // TODO: reconsider this... 
#endif
    if(shade > SHADE_COUNT) shade = SHADE_COUNT;
    else if(shade < 1) shade = 1;

    shade = SHADE_COUNT - shade;
    return shade;
}
//
//Get light fraction, based on the_fish's code:
#ifdef USE_ADVSHADES
double inline GetLightFrag(int x, int y, int tx, int ty, byte sx, byte sy){
   
   double tx1, ty1, tx2, ty2;

   if (tx > x || ty > y){
       tx1 = 0.5-((double)sx/TEXTURESIZE);
       ty1 = ((double)sy/TEXTURESIZE)-0.5;
   }else{
       tx1 = ((double)sx/TEXTURESIZE)-0.5;
       ty1 = 0.5-((double)sy/TEXTURESIZE);
   }
   
   tx2 = 1-fabs(tx1);
   ty2 = 1-fabs(ty1);
   

   if(tx1 > 0){ 
      if(ty1 > 0)
         return ((lightMap[x][y]   * (tx2*ty2)) + (lightMap[x+1][y]   * (tx1*ty2)) +
                 (lightMap[x][y+1] * (tx2*ty1)) + (lightMap[x+1][y+1] * (tx1*ty1)));    
      else{
         ty1 *= -1;
         return ((lightMap[x][y]   * (tx2*ty2)) + (lightMap[x+1][y]   * (tx1*ty2)) +
                 (lightMap[x][y-1] * (tx2*ty1)) + (lightMap[x+1][y-1] * (tx1*ty1)));            
      }
   }else{
      tx1 *= -1;
      if(ty1 > 0)
         return ((lightMap[x][y]   * (tx2*ty2)) + (lightMap[x-1][y]   * (tx1*ty2)) +
                 (lightMap[x][y+1] * (tx2*ty1)) + (lightMap[x-1][y+1] * (tx1*ty1)));          
      else{
         ty1 *= -1;
         return ((lightMap[x][y]   * (tx2*ty2)) + (lightMap[x-1][y]   * (tx1*ty2)) +
                 (lightMap[x][y-1] * (tx2*ty1)) + (lightMap[x-1][y-1] * (tx1*ty1)));            
      }    
   }  return 0;       
}
//
//Get the intensity of flashlight, based on the_fish's code:
double inline FlashlightIntensity(int scale, double radius){
   return (scale*scale) * (1 - (radius/12)*(radius/12)) / (1000);       
}
//
//Fill the lightmap from either objects or 4th-plane:
void InitLevelShadeMap(){
   for(int x=0;x<MAPSIZE;x++)
   { for(int y=0;y<MAPSIZE;y++)
     {   
#ifdef USE_4THPLANE
         lightMap[x][y] = MAPSPOT(x,y,3);
#else
          switch(MAPSPOT(x,y,1))
          {   
              case 26:   //Floor lamp
              case 27:   //Chandelier
              case 37:   //Green lamp
#ifdef WOLF
              case 68:   //Stove       
#else
#ifdef SPEAR
              case 63:   //Red lamp
              case 74:   //Spear of Destiny
#endif
#endif 
                 //Light sources cast light:
                   lightMap[x-1][y]=215; lightMap[x+1][y]=215;
                   lightMap[x][y-1]=215; lightMap[x][y+1]=215;
                   lightMap[x][y]=255;
               break; 
           }   
         //Generic shadevalues for the level
          // if(lightMap[x][y] == 0) lightMap[x][y] = 150;   
#endif  
       }
   }  
}
#endif
#endif
