module Main;

// amd xp2500+: 1056fps (JIT), 117fps (interpreted)

use tksdl;
use tkopengl;

int framecount=0;
int b_vsync=0;
int b_fpslimit=1;
FireRenderer renderer;

class FireRenderer
{
      // ---- texture size in pixels
   define int DEFAULT_TEXTURE_WIDTH  =128;
   define int DEFAULT_TEXTURE_HEIGHT =32;
   define int DECAY_RATE=82;
   define int PAL_SIZE =1024;

   private int        texture_width =DEFAULT_TEXTURE_WIDTH;
   private int        texture_height=DEFAULT_TEXTURE_HEIGHT;
   private Texture    firetexa;
   private Texture    firetexb;
   private Texture    firetexc;
   private Texture    firetexap;
   private Texture    firetexbp;
   private IntArray   palette;
   private IntArray   paletteb;
   private float      palette_sin1,palette_sin2,palette_sin3;
   private float      palette_sinb1,palette_sinb2,palette_sinb3;
   private float      palette_sinb1i,palette_sinb2i,palette_sinb3i;

   FireRenderer() { // ---- constructor 
      initTextures();
      initPalette();
      calcPalette();
      calcPaletteB();
   }
   
   public method setSize(int _w, int _h) {
      // ---- set the size of the render texture
      if(_w<=0)
         _w=DEFAULT_TEXTURE_WIDTH;
      if(_h<=0)
         _h=DEFAULT_TEXTURE_HEIGHT;
      if (_w!=texture_width)||(_h!=texture_height)
      {
         texture_width =_w;
         texture_height=_h;
         initTextures();
      }
   }
   
   public method getTexture()/*:Texture*/ {
      // ---- return the render texture
      return firetexc;
   }

   private method initPalette() {
      palette.alloc(PAL_SIZE);
      palette.addEmpty(PAL_SIZE);
      
      paletteb.alloc(PAL_SIZE);
      paletteb.addEmpty(PAL_SIZE);

      palette_sin1=0;
      palette_sin2=0;
      palette_sin3=0;
      palette_sinb1=0;
      palette_sinb2=0;
      palette_sinb3=0;
      calcPalette();
   }


   private method calcPalette() {
      IntArray pal <= palette;
      compile
      {
         float c,d,cstep;
         int ci;
         
         int ps=PAL_SIZE*0.52;
         
         float psin1=0.872546*0.8 + 0.2*sin(palette_sin1);
         palette_sin1+=0.0023512*7;
         while(palette_sin1>=2PI)palette_sin1-=2PI;
         
         float psin2=0.958625*0.8 + 0.2*sin(palette_sin2);
         palette_sin2+=0.004123*5;
         while(palette_sin2>=2PI)palette_sin2-=2PI;
         
         float psin3=0.476*0.8 + 0.2*sin(palette_sin3);
         palette_sin3+=0.0011*17;
         while(palette_sin3>=2PI)palette_sin3-=2PI;
         
         cstep=1.0/(ps/3.0);
         c=0;
         float a=0;
         float astep=(2PI/(ps/3.0))*8;
         for(int i=0; i<(ps/3.0); i++)
         {
            ci=(c-( (1.0-c)*c*0.10*(0.5+0.5*(psin1*0.6+psin2*0.2+psin3*0.2)*sin(a))))*255;
            clamp ci 0 255;
            pal[i]=argb(ci,64+ci/4,0,0);
            c+=cstep;
            a+=astep;
         }
         c=0;
         a=0;
         cstep=1.0/( (ps/2.0)-(ps/3.0) );
         astep=2PI*cstep*16;
         for(; i<(ps/2.0); i++)
         {
            ci=(c-( (1.0-c)*0.10*(0.5+0.5*(psin1*0.3+psin2*0.3+psin3*0.4)*sin(a))))*255;
            clamp ci 0 255;
         pal[i]=argb(ci,128+ci/2,ci,0);
         c+=cstep;
         a+=astep;
         }
         c=0;
         cstep=1.0/(PAL_SIZE-i);
         astep=2PI*cstep*32;
         a=0;
         for(; i<PAL_SIZE; i++)
         {
            ci=(c-( (1.0-c)*0.90*(0.5+0.5*(psin1*0.3+psin2*0.3+psin3*0.4)*sin(a))))*255;
            pal[i]=argb(255,255,255,ci);
            c+=cstep;
         }
      }
   }

   private method calcPaletteB()  {
      IntArray palb <= paletteb;
      compile
      {
         float c,d,cstep;
         int ci;
         
         float psin1a=palette_sinb1;
         palette_sinb1+=0.17812;
         while(palette_sinb1>=2PI)palette_sinb1-=2PI;
         
         float psin2a=palette_sinb2;
         palette_sinb2+=0.0154123;
         while(palette_sinb2>=2PI)palette_sinb2-=2PI;
         
         float psin3a=palette_sinb3;
         palette_sinb3+=0.0138711;
         while(palette_sinb3>=2PI)palette_sinb3-=2PI;
         
         float psin1,psin2,psin3;
      
         cstep=1.0/PAL_SIZE;
         c=0;
         float a=0;
         float astep=2PI*cstep*64;
         for(int i=0; i<PAL_SIZE; i++)
         {
            psin1=sin(psin1a);
            psin2=sin(psin2a);
            psin3=sin(psin3a);
            
            ci=(c*(0.5+0.5*(psin1*psin2*psin3)*sin(a)))*PAL_SIZE;
            clamp ci 0 PAL_SIZE;
            palb[i]=i*0.99+(ci*0.01);
            c+=cstep;
            a+=astep;
            psin1a+=0.23244;
            psin2a+=0.18909;
            psin3a+=0.30034235;
         }
      }
   }

   private method initTextures() {
         // ---- initialize both fire render texture and the output texture
      firetexa.alloc(texture_width, texture_height, 4); // 4= 4bytes per pixel => 32bit ARGB
      firetexa.flags=0;
      firetexa.clear(0);
      firetexb.alloc(texture_width, texture_height, 4);
      firetexb.flags=0;
      firetexb.clear(0);
      firetexc.alloc(texture_width, texture_height, 4);
      firetexc.flags=0;
      firetexc.clear(0);
      firetexap<=firetexa;
      firetexbp<=firetexb;
         // ---- init last row 
      int x=texture_width*(texture_height-1);
      loop(texture_width) {
         firetexa[x++]=rnd(PAL_SIZE);
      }
      firetexc.upload();

   }

   public method render() {
      
      calcPalette();
      calcPaletteB();
      
      // ---- swap front/back buffer
      Texture t<=firetexap; 
      firetexap<=firetexbp;
      firetexbp<=t;
      // ---- ^^ this has to be done outside of JIT blocks
      // ---- now map object fields to variables
      // ---- to increase speed of JIT block
      Texture ta<=firetexap;
      Texture tb<=firetexbp;
      Texture tc<=firetexc;
      IntArray pal<=palette;
      
      IntArray palb<=paletteb;
      
      if(1) {
         // ---- blur and move upwards.
         // ---- the result is written to the render "backbuffer"
         // ---- a palette lookup is done for the result pixels and written to
         // ---- the final output texture (firetexc) 
         int n =texture_height-2;
         int pa=texture_width*(texture_height-n-1);
         int pb=texture_width*(texture_height-n-2);
         int pc=texture_width*(texture_height-n-1);
         int q1=texture_width*(texture_height-n-2);
         int q2=texture_width*(texture_height-n);
         int q3=texture_width*(texture_height-n-1)-1;
         int q4=texture_width*(texture_height-n-1)+1;
         compile loop(n)
         {
            loop(texture_width-2)
            {
               int c=
                  (ta[q1++]+ta[q2++]+
                   ta[q3++]+ta[q4++])*0.248;
               ta[pa++]=c;
               tb[pb++]=palb[c];
               tc[pc++]=pal[c];
            }
            pa+=2;
            pb+=2;
            pc+=2;
            q1+=2;
            q2+=2;
            q3+=2;
            q4+=2;
         }
      }
         // ---- process last line ----
      int idx=texture_width*(texture_height-1);
      loop(texture_width)
      {
         int z=tb[idx];
         if(z>=DECAY_RATE)
            tb[idx]=z-DECAY_RATE;
         else
            tb[idx]=rnd(PAL_SIZE);
         idx++;
      }

   }
   
}


function onDraw {
      // ---- draws the screen (and the render texture)
   zglInit2D(Viewport.width, Viewport.height);
   glEnable(GL_TEXTURE_2D);
   glDisable(GL_BLEND);
   glDisable(GL_DEPTH_TEST);
   
   renderer.render();
   Texture t;
   t<=renderer.getTexture();
   t.update();
   t.bind();

   glColor4f(0,0,1,1);
   glBegin(GL_QUADS);
   glTexCoord2f( 2/t.sx, 0);
   glVertex2f  ( 0, 0);
   glTexCoord2f( 2/t.sx+(tcfloat(t.sx-4)/t.sx), 0);
   glVertex2f  ( Viewport.width,  0);
   glTexCoord2f( 2/t.sx+(tcfloat(t.sx-4)/t.sx), (tcfloat(t.sy-4)/t.sy));
   glVertex2f  ( Viewport.width, Viewport.height);
   glTexCoord2f( 2/t.sx, (tcfloat(t.sy-4)/t.sy));
   glVertex2f  ( 0, Viewport.height);
   glEnd();

   if(b_vsync)
      Viewport.waitVBlank();

   if((framecount++&127)==0) 
	{ // ---- output frames-per-sec stats every 128 frames
      trace "fprec="+FPS.precision+" fps="+FPS.real;
	}
}

function onReopen() {
   FPS.reset();
}

function onKeyboard(Key _k) {
   switch(_k.pressed)
   {
      case VKEY_ESCAPE:
         SDL.exitEventLoop();
         break;
      case 'v':
         b_vsync=1-b_vsync;
         print "vertical set to "+b_vsync;
         break;
      case 'f':
         b_fpslimit=1-b_fpslimit;
         updateFPSLimit();
         break;
   }
}

function updateFPSLimit() {
   if(b_fpslimit)
   {
      FPS.limit=30;
      b_vsync=0;
      print "vertical set to "+b_vsync;
   }
   else
      FPS.limit=0;
   print "fps limit set to "+b_fpslimit+" ("+FPS.limit+" fps).";
}

function main {
   if(Arguments.numElements)
   {
         // ---- benchmark mode (no graphics output)
      int t=milliSeconds();
      loop(1024)
      {
         renderer.render();
      }
      t=milliSeconds()-t;
      print "t(ms)="+t;
      print "fps="+ (1000/(t/1024.0));
   }
   else
   {
      Viewport.openWindow(FireRenderer.DEFAULT_TEXTURE_WIDTH*3, FireRenderer.DEFAULT_TEXTURE_HEIGHT*3);
      Viewport.caption="fire - | [escape] exit";
      FPS.tickInterval=1000.0/50;   // set timer speed to 50Hz
      FPS.tickBuffer=0;             // catch up 0 missed "ticks"
      updateFPSLimit();
      onReopen();                                        
      use callbacks;                                     
      SDL.eventLoop();
   }
}