#include <stdio.h>
#include <math.h>
#include "map.hpp"

extern ALLEGRO_COLOR pal[256];
extern char*rawmap[];
extern uint32_t score;

void PutSpr(int,int,int);

Map::Map(){
  mh=0;mv=0;bmp=0;mapgris=0;mapcolor=0;
}
Map::~Map(){int i;
  for(i=0;i<DEPTH;i++)free(Grad[i]);
  if(bmp)al_destroy_bitmap(bmp);
  free(mh);free(mv);
}

int Map::Init(){
  mv=(uint8_t*)malloc(737280);if(!mv)return -1;
  mh=(uint16_t*)malloc(1474560);if(!mh)return -2;
  bmp=al_create_bitmap(1920,384);if(!bmp)return -3;
  mapgris=al_create_bitmap(1920,384);if(!mapgris)return -4;
  mapcolor=al_create_bitmap(1920,384);if(!mapcolor)return -5;
  mapdot=(uint8_t*)malloc(737280);if(!mapdot)return -6;
  //Reserva Gradientes
  for(int zx=0;zx<DEPTH;zx++){
    Grad[zx]=(float*)malloc(((1920>>zx)+1)*((1920>>zx)+1)*sizeof(float)*2);
    if(!Grad[zx]){printf("FALLO %d\n",zx);return -7;}
  }
  return 0;
}

void Map::Create(){int i,j,zx,zy;
  al_set_target_bitmap(bmp);
  al_clear_to_color(pal[0]);
  for(i=0;i<6;i++)for(j=0;j<20;j++){
    for(zx=0;zx<2;zx++)for(zy=0;zy<3;zy++){
      PutSpr((i<<6)+(zx<<5),(j*96)+(zy<<5),10);
    }
  }
  for(i=0;i<6;i++)for(j=0;j<20;j++){
    switch(rawmap[i][j]){
    case '_':
      for(zx=0;zx<3;zx++)PutSpr((i<<6)+58,(j*96)+(zx<<5),7);
      break;
    case 'H':
      for(zx=0;zx<3;zx++)PutSpr((i<<6)+58,(j*96)+(zx<<5),7);
      PutSpr((i<<6)+26,(j*96)+32,9);
      PutSpr((i<<6)-6,(j*96)+32,8);
      break;
    case '|':
      for(zx=0;zx<3;zx++)PutSpr((i<<6)+58,(j*96)+(zx<<5),7);
      PutSpr((i<<6)+26,(j*96)+32,5);
      PutSpr((i<<6),(j*96)+32,6);
      break;
    }
  }
  al_set_target_bitmap(mapgris);
  al_draw_bitmap(bmp,0,0,0);
  GenGrad();
  for(zx=0;zx<384;zx++)for(zy=0,i=0;zy<1920;zy++)
    mh[zx*1920+zy]=perlin(zx,zy,DEPTH-1)*360;
  ALLEGRO_LOCKED_REGION*lreg;
  ALLEGRO_COLOR ztmp;float zr,zg,zb;
  lreg=al_lock_bitmap(bmp,ALLEGRO_PIXEL_FORMAT_RGB_888,ALLEGRO_LOCK_READWRITE);
  char*zdat=(char*)lreg->data;
  //for(zx=0;zx<384;zx++)for(zy=0;zy<1920;zy++){
  for(zx=0;zx<384;zx++){for(zy=0,i=0;zy<1920;zy++,i+=3)mv[zx*1920+zy]=zdat[i];zdat+=lreg->pitch;}
  zdat=(char*)lreg->data;
  for(zx=0;zx<384;zx++){for(zy=0,i=0;zy<1920;zy++){
    al_color_hsv_to_rgb(mh[zx*1920+zy],1,mv[zx*1920+zy]/255.0,&zr,&zg,&zb);
    zdat[i++]=zr*255;
    zdat[i++]=zg*255;
    zdat[i++]=zb*255;
  }zdat+=lreg->pitch;}
  al_unlock_bitmap(bmp);
  al_set_target_bitmap(mapcolor);
  al_draw_bitmap(bmp,0,0,0);
  dotcolor=737280;for(zx=0;zx<737280;zx++)mapdot[zx]=1;
}

void Map::Drain(int zx1,int zy1,int zx2,int zy2){int zax,zay,zaz;
  if(zx1<0)zx1=0;if(zx1>383)zx1=383;
  if(zx2<8)zx2=8;if(zx1+zx2>383)zx1=384-zx2;
  if(zy1<0)zy1=0;if(zy1>1919)zy1=1919;
  if(zy2<8)zy2=8;if(zy1+zy2>1919)zy1=1919-zy2;
  for(zax=zx1;zax<zx1+zx2;zax++){zaz=zax*1920+zy1;for(zay=zy1;zay<zy1+zy2;zay++,zaz++){
    if(mapdot[zaz]){mapdot[zaz]=0;dotcolor--;}}
  }al_draw_bitmap_region(mapgris,zy1,zx1,zy2,zx2,zy1,zx1,0);
}

void Map::Heal(int zx1,int zy1,int zx2,int zy2){int zax,zay,zaz;
  if(zx1<0)zx1=0;if(zx1>383)zx1=383;
  if(zx2<8)zx2=8;if(zx1+zx2>383)zx1=384-zx2;
  if(zy1<0)zy1=0;if(zy1>1919)zy1=1919;
  if(zy2<8)zy2=8;if(zy1+zy2>1919)zy1=1919-zy2;
  for(zax=zx1;zax<zx1+zx2;zax++){zaz=zax*1920+zy1;for(zay=zy1;zay<zy1+zy2;zay++,zaz++)
    if(!mapdot[zaz]){mapdot[zaz]=1;dotcolor++;score++;}
  }al_draw_bitmap_region(mapcolor,zy1,zx1,zy2,zx2,zy1,zx1,0);
  //al_draw_filled_rectangle(zy1,zx1,zy1+zy2,zx1+zx2,pal[1]);
}

//============================================================================//
// PERLIN NOISE                                                               //
//============================================================================//

void Map::GenGrad(){int zz,zzx,zzy,zx,zy;
  float a,b,f;
  //Prepara Gradientes
  for(zz=0;zz<DEPTH;zz++){zzx=(384>>zz)+1;zzy=(1920>>zz)+1;
    //printf("T:%d - %d\n",zzx,zzy);
    for(zx=0;zx<zzx;zx++)for(zy=0;zy<zzy;zy++){
      a=((rand()%2000)-1000)/1000.0;
      b=((rand()%2000)-1000)/1000.0;
      f=sqrt(a*a+b*b);
      Grad[zz][(zx*zzx+zy)<<1]=a/f;
      Grad[zz][((zx*zzx+zy)<<1)+1]=b/f;
      //printf("%d,%d: %f\n",zx,zy,Grad[zz][(zx*zzx+zy)<<1]);
    }
  }
}

float Map::perlin(int x,int y,int zd){int zz,zz2=1;float f=0;
  zz=0;
  for(zz=zd;zz>=1;zz--){
    f+=Snoise(x,y,zz)/zz2;zz2<<=1;
  }
  return f;
  //f/=1.9375;return f;
}

float Map::Snoise(int x,int y,int oct){int zx,zy,zx2,zy2,zo1=1<<oct,zo3=zo1-1;
  float*a,*b,*c,*d,zo2=zo1,rx,ry,cx,cy;
  zx=x/zo1;zy=y/zo1;zx2=zx+1;zy2=zy+1;
  a=Grad[oct]+((zx*((384>>oct)+1)+zy)<<1);
  b=Grad[oct]+((zx2*((384>>oct)+1)+zy)<<1);
  c=Grad[oct]+((zx*((384>>oct)+1)+zy2)<<1);
  d=Grad[oct]+((zx2*((384>>oct)+1)+zy2)<<1);
  //printf("%d,%d: %f\n",x,y,a[0]);
  rx=(x&zo3)/(float)zo3;ry=(y&zo3)/(float)zo3;
  a[0]*rx+a[1]*ry;
  cx=rx*rx*(3.-2.*rx);
  cy=ry*ry*(3.-2.*ry);
  return lerp(lerp(a[0]*rx+a[1]*ry,b[0]*(rx-1.)+b[1]*ry,rx),
    lerp(c[0]*rx+c[1]*(ry-1.),d[0]*(rx-1.)+d[1]*(ry-1.),rx),ry);
}

// Function to linearly interpolate between a0 and a1
// Weight w should be in the range [0.0, 1.0]
float Map::lerp(float a0,float a1,float w){
  //return (1.0 - w)*a0 + w*a1;
  //return a0 + w*(a1 - a0);
  float f=w*w*(3-2*w);return a0*(1-f)+a1*f;
  //float f=6*pow(w,5)-15*pow(w,4)+10*pow(w,3);return a0*(1-f)+a1*f;
  //float f=-20*pow(w,7)+70*pow(w,6)-84*pow(w,5)+35*pow(w,4);return a0*(1-f)+a1*f;
}
