/*
  inductiemachine
  by limp ninja

  bruce d. lee
  max power
  
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#ifdef _MSC_VER			// visual c workarounds
#define M_PI 3.1415926535
#include <windows.h>
#endif

#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include <cmath>
#include <cstdlib>
#include <iostream>
#include <vector>

#include <fmod.h>
#include <fmod_errors.h>

float g_squaresIntensity = 0;
float g_flowerOffset = 0;
bool g_mapIteration = false;
float g_flowerIntensity[3] = { 0, 0, 0};
int g_flowerIdx = 0;
float g_hexagonOffset = 0;
float g_crossIntensity = 0;
int g_hexInnerCount = 0;
GLuint g_textures[7];
bool g_randomizeText = false;
float g_sectionRadius = 0;
float g_cowbell = 0;
int g_cowSolution = 0;
int g_cowBellSolution = 0;
int g_rndUser = 0;


GLuint loadTextureFromFile(const char *filename) {

  GLuint m_glTexture;
  glGenTextures(1, &m_glTexture);

  glBindTexture(GL_TEXTURE_2D, m_glTexture);


  SDL_Surface *textureData = IMG_Load(filename);
  if (!textureData)
    {
      char buf[100];
      sprintf(buf,"error loading texture '%s'", filename );
      perror(buf);
      exit(1);
    }
  SDL_LockSurface(textureData);


  glTexImage2D(GL_TEXTURE_2D,
               0,
               3,
               textureData->w,
               textureData->h,
               0,
               GL_RGB,
               GL_UNSIGNED_BYTE,
               textureData->pixels);


  SDL_UnlockSurface(textureData);
  SDL_FreeSurface(textureData);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  return m_glTexture;
}



void F_CALLBACKAPI instCallback(FMUSIC_MODULE *mod,
                                unsigned char param) {

  
  //  std::cout << (int) param << ": ";
  if (param == 1) {
    //    std::cout << "BASSDRUM\n";
    g_squaresIntensity = 1.0f;
    g_cowSolution++;
    g_hexagonOffset += .4;
    g_sectionRadius = 1.0f;
    


  }
  else

    if (param == 2 || param == 3 || param == 4) {
      //      std::cout << "COOOOWBEELLLL!\n";
      g_cowbell = 1.0f;
      g_cowBellSolution ++;
    }
  
  if (param ==6) {
    //    std::cout << "SUBBASS\n";
  }
  else 
    if (param == 5) {
      g_randomizeText = true;
      //      std::cout << "snare!\n";
      g_rndUser++;
    }


  if (param == 14 || param == 12) {
    //    std::cout << "PLIEP\n";
    //    g_hexagonOffset += .4;
    g_crossIntensity = 1.0f;
    g_hexInnerCount += 1;
  }

  if (param == 13) {
    //    std::cout << "MWEP\n";
    g_squaresIntensity = 1.0f;
    g_crossIntensity = 1.0f;
    g_hexInnerCount +=1;
  }
  else
    if (param == 14) {
      //      std::cout << "SWOEP\n";
      g_flowerOffset += 341.0f;
      g_hexInnerCount +=1;
    }
    else
      if (param == 16) {
        //        std::cout << "PJIEW\n";
        g_flowerIntensity[g_flowerIdx % 3] = 1.0f;

        g_flowerIdx++;


      }
  if (param == 17) {
    //    std::cout << "METALKNERP\n";
    g_hexInnerCount++;

    if (FMUSIC_GetRow(mod) < 28) 
      g_mapIteration = true;


  }

  else {
    //    std::cout << "unknown\n";
  }


}


struct Vector2 { 
  
  Vector2(float x, float y) :
    x(x), y(y) { 
  };

  Vector2(): 
    x(0), y(0)
  {};

  Vector2(const Vector2 &rhs) {
    x = rhs.x;
    y = rhs.y;
  }

  Vector2 rotate(float angle) const {
    Vector2 rotated(0,0);
    rotated.x = cos(angle) * x + sin(angle) * y;
    rotated.y = - sin(angle) * x + cos(angle) * y;
    return rotated;
  };

  Vector2 operator + (const Vector2 &rhs) {
    return Vector2(x + rhs.x, y + rhs.y);
  };

  Vector2 operator * (const float& f) {
    return Vector2(x*f, y*f);
  };

  Vector2 operator - (const Vector2 &rhs) {
    return Vector2(x - rhs.x, y - rhs.y);
  };

  void gl()
  {
    glVertex2f( x,y );
  }

  float x,y ;
};

struct Color {
  Color(float r, float g, float b)
    : r(r), g(g), b(b) {};
  float r, g, b;
};

struct Segment {
  Segment(float x, float y, float angle)
    : position(x,y), angle(angle) {} ;
  Vector2 position;
  float angle;
};




void drawFilledCircle(float radius, float x, float y, int sides, float rot = 0);
void drawSegment(int segmentType, const Segment &segment) {

  glDisable(GL_BLEND);

  const float thickness = 0.015;
  const float length = .05;
  glColor3f(1,1,1);
  glBegin(GL_QUADS);
  
  Vector2 center(segment.position);
  Vector2 tdir(0, thickness);
  Vector2 ldir(length, 0);

  tdir = tdir.rotate(segment.angle);
  ldir = ldir.rotate(segment.angle);

  //  float x0 = segment.position.x - length;
  //  float x1 = segment.position.x + length;
  //  float y0 = segment.position.y - thickness;
  //  float y1 = segment.position.y + thickness;

  glColor3f(0,0,0);
  Vector2 v0, v1, v2, v3;

  v0 = center - tdir*1.2 ;
  v1 = center - tdir*1.2 + ldir*1.1*2;
  v2 = center + tdir*1.2 + ldir*1.1*2;
  v3 = center + tdir*1.2 ;  

  //  glVertex2f(v0.x, v0.y);
  //  glVertex2f(v1.x, v1.y);
  //  glVertex2f(v2.x, v2.y);
  //  glVertex2f(v3.x, v3.y);


  glColor3f(1,1,1);

  v0 = center - tdir ;
  v1 = center - tdir + ldir+ldir;
  v2 = center + tdir + ldir+ldir;
  v3 = center + tdir ;




  glVertex2f(v0.x, v0.y);
  glVertex2f(v1.x, v1.y);
  glVertex2f(v2.x, v2.y);
  glVertex2f(v3.x, v3.y);

  glEnd();

  glDisable(GL_DEPTH_TEST);

  glBegin(GL_TRIANGLES);
  drawFilledCircle(thickness, center.x, center.y, 10);
  drawFilledCircle(thickness, (center+ldir*2).x, (center+ldir*2).y, 10);
  glColor3f(1,0,0);
  Vector2 dotpointl,dotpointr;
  double fac = 0.5;
  dotpointl = center +ldir*(1.0-fac);
  dotpointr = center +ldir*(1.0+fac);
  drawFilledCircle(.0055, dotpointl.x, dotpointl.y, 10);
  drawFilledCircle(.0055, dotpointr.x, dotpointr.y, 10);
  //  drawFilledCircle(.0045, segment.position.x + length * .8, segment.position.y, 10);


  
  

  glEnd();
  
}

void drawShadedCircle(float radius, 
                      const Color &center,
                      const Color &outside,
                      float x, 
                      float y,
                      bool additive, int sides = 3) {


  if (additive) {
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
  }


  glBegin(GL_TRIANGLES);

  float r = 2*3.1415926535 / sides;
  //  float radius = .2;




  for (int j = 0; j < sides; ++j) {
    

    float x0 = std::cos( j*r) * radius + x;
    float y0 = std::sin(j*r) * radius * 4.0 / 4.0+ y;
    float x1 = std::cos(j*r + r) * radius + x;
    float y1 = std::sin(j*r + r) * radius * 4.0/4.0 + y;

    // center
    glColor3f(center.r, center.g, center.b);
    glVertex2f(x, y);

    // outside
    glColor3f(outside.r, outside.g, outside.b);
    glVertex2f(x0, y0);
    glVertex2f(x1, y1);


  }
  glDisable(GL_BLEND);

  glEnd();
}


void drawFilledRing(float innerradius, float radius, float x, float y, int sides, float start=0, float end = 3.1415926535*2) {

  float rot = start;
  float deltar = end - start;

  float r = deltar / sides;
  for (int i = 0; i < sides; ++i) {
    
    float x0 = cos(rot + i * r) * innerradius + x ;
    float y0 = sin(rot + i * r) * innerradius + y;
    float x1 = cos(rot + i * r + r) * innerradius + x;
    float y1 = sin(rot + i * r + r) * innerradius + y;    
    float x2 = cos(rot + i * r) * radius + x ;
    float y2 = sin(rot + i * r) * radius + y;
    float x3 = cos(rot + i * r + r) * radius + x;
    float y3 = sin(rot + i * r + r) * radius + y;    

    glVertex2f(x0, y0);
    glVertex2f(x1, y1);
    glVertex2f(x3, y3);
    glVertex2f(x2, y2);
  }
}




void drawFilledCircle(float radius, float x, float y, int sides, float rot) {

  float r = 2* 3.1415926535 / sides;
  for (int i = 0; i < sides; ++i) {
    
    float x0 = cos(rot + i * r) * radius + x ;
    float y0 = sin(rot + i * r) * radius + y;
    float x1 = cos(rot + i * r + r) * radius + x;
    float y1 = sin(rot + i * r + r) * radius + y;    
    
    glVertex2f(x, y);
    glVertex2f(x0, y0);
    glVertex2f(x1, y1);
  }
}


void drawCircle(float radius, float x, float y, int sides, float rot = 0) {

  float r = 2* 3.1415926535 / sides;
  for (int i = 0; i < sides; ++i) {

    float x0 = cos(rot + i * r) * radius + x ;
    float y0 = sin(rot + i * r) * radius*4.0/4.0 + y;
    float x1 = cos(rot + i * r + r) * radius + x;
    float y1 = sin(rot + i * r + r) * radius*4.0/4.0 + y;    

    glVertex2f(x0, y0);
    glVertex2f(x1, y1);
  }

}

void drawCircles(float time) {

  glBegin(GL_LINES);


  for (int i = 0; i < 10; ++i) {

    for (int j = 2; j < 6; ++j) {

    
      float x = i / 10.0f + 1.0 / 20.0f;
      float y = (j / 10.0f + 1.0 / 20.0f )* (4.0/4.0);
      

      float intensity = cos(i*j + time*2) * 0.5 + 0.5;

      if (intensity > .5) {
        glColor3f(.6, .3, .1);


        drawCircle(.01*intensity, x , y, 20);
      }

      if (intensity > .7)
        drawCircle(.02*intensity, x , y, 20);
      



    
    }
  }


  glEnd();
}


void drawHexagons(double time) {
  
  glEnable(GL_POLYGON_SMOOTH);
  glBlendFunc( GL_SRC_ALPHA_SATURATE, GL_ONE );
  glEnable( GL_BLEND );

  glColor3f(1,1,1);



  glBegin(GL_LINES);

  for (int j = 0; j < 5; j++)
    for (int i =0; i < 8; i++) {

      double y = j;

      if (i&1)
        y += .5;

      float r = std::cos(1.0*g_hexagonOffset+i+j)*.025+.025;
      float a = std::cos(g_hexInnerCount*100.0 + i*10 + j*10);
      if (r > .025) {
        drawCircle(.05, .25 + i/12.4, .25 + y/8.55, 6, 3.1415926535 / 3);
        if(a > 0)
          drawCircle(.025, .25 + i/12.4, .25 + y/8.55, 20, 3.1415926535 / 3);
      }
    }
  
  glEnd();
  glDisable(GL_BLEND);
  glDisable(GL_LINE_SMOOTH);
}

void drawSun(double time, 
             float radius, 
             const Color &center,
             const Color &outside,
             bool additive,
             float x, 
             float y) {


  if (additive) {
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
  }


  glBegin(GL_TRIANGLES);
  const int sides = 20;

  float r = 3.1415926535 / sides;
  //  float radius = .2;




  for (int j = 0; j < sides; ++j) {
    

    float x0 = std::cos(time*.3 + 2*j*r) * radius + x;
    float y0 = std::sin(time*.3 + 2*j*r) * radius + y;
    float x1 = std::cos(time*.3 + 2*j*r + r) * radius * 4.0 / 4.0 + x;
    float y1 = std::sin(time*.3 + 2*j*r + r) * radius * 4.0 / 4.0 + y;


    // center
    glColor3f(center.r, center.g, center.b);
    glVertex2f(x, y);

    // outside
    glColor3f(outside.r, outside.g, outside.b);
    glVertex2f(x0, y0);
    glVertex2f(x1, y1);


  }
  glDisable(GL_BLEND);

  glEnd();
}

void drawSun(double time) {
  float intensity = .2 + (cos(time*.3) * .5) + .5 ;
  Color center(0,0,0);
  Color outside(intensity, 0, 0);

  drawSun(time, .2,  center, outside, false, .5, .5);
}

void drawFlowers(double time) {

  static double lastTime;
  double delta = time - lastTime;


  unsigned int pattern[32*4];
  for (int i = 0 ; i < 32*4; ++i) {
    pattern[i] = rand();
  }
  glPolygonStipple((GLubyte*)pattern);
        
  Color outside(0,0,0);
  glEnable(GL_POLYGON_STIPPLE);
  
  
  for (int i = 0; i < 3; ++i) {
          
    float c = g_flowerIntensity[i];

    Color center(c+.3  + cos(time+i)*.1,c + .2, c +.1);
    drawSun(time * 1.321, 
            .4 + cos(time*2.1329+i), 
            center, 
            outside, 
            true, 
            cos(time+i+g_flowerOffset)*.5+.5,
            cos(time+i+g_flowerOffset)*.1+.5);
    
  }

  glDisable(GL_POLYGON_STIPPLE);


  for (int i =0; i < 3; ++i)
    g_flowerIntensity[i] *= powf(0.01, delta);
  lastTime = time;


}


void drawLines(double t) {

  glBegin(GL_LINES);
  
  double time = t * .3;


  float r = (std::cos(time * 0.321) * .2 + .2);
  for (int j = 0; j <100; ++j) {
    float x = (std::cos(time + j) +1 ) * .5;
    float m = (x - .5) * (x - .5) * 2;
    glColor3f(m + cos(time + j * .1), 0, 0);
    glVertex2f(x, r*m + .2);
    glVertex2f(x, 1-r*m - .2);
  }
  glEnd();
}


void drawCross(float radius, float x, float y) {
  
  glVertex2f(x - radius, y);
  glVertex2f(x + radius, y);
  
  glVertex2f(x, y - radius * 4.0/4.0);
  glVertex2f(x, y + radius * 4.0/4.0);
  
}


void drawCrosses(double time) {

  static double lasttime = -1;

  double delta = time - lasttime;

  if (g_crossIntensity> .5) {


    glColor3f(1,1,1);
    glBegin(GL_LINES);
    static int mask = 0;

    static float radius = .02;

    if (std::rand()% 100 == 5) {
      radius = (float)std::rand() / (float)RAND_MAX;
      radius *= .03;
      radius += .01;
      
    }

    for (int i = 0; i < 11; ++i) {
      if (mask & (1<<i))
        drawCross(radius, i / 10.0, .5);
 
    }

    if (std::rand() % 100 == 5) {
      mask = rand();
    }


    glEnd();
  }

  g_crossIntensity *= powf(.01, delta);
  lasttime = time;

}


void drawHypnoQuad(float width, float height) {

  float x0 = -width + .5;
  float y0 = -height + .5;
  float x1 = width + .5;
  float y1 = height + .5;


  glVertex2f(x0, y0);
  glVertex2f(x1, y0);

  glVertex2f(x1, y0);
  glVertex2f(x1, y1);

  glVertex2f(x1, y1);
  glVertex2f(x0, y1);

  glVertex2f(x0, y1);
  glVertex2f(x0, y0);

}





void drawHypnoQuads(double time) {

  static double lasttime = -1;


  double delta = time - lasttime;


  

  glBegin(GL_LINES);


  float t = time * 2.0f;
  float tw = time * 4.3252;


  for (int j = 0; j < 20; ++j) {
    glColor3f(g_squaresIntensity * cos(j+t) * cos(j+t), 0, 0);
    drawHypnoQuad(.05 * cos(j + tw)+.25, ( .05 * cos(j + tw)+ .25));
  }

  g_squaresIntensity *= powf(0.01, delta);

  glEnd();

  lasttime = time;

}

void drawMetroMap(double time, int order) {

  static unsigned int polyStipple0[32*4];
  for (int y = 0; y < 32; y++) {
    polyStipple0[31-y] = (1<<(y&3))*0x11111111;
  }
  glPolygonStipple((GLubyte*)polyStipple0);
  glEnable(GL_POLYGON_STIPPLE);

  static int lastOrder = -1;



  static int count = 1;
  static std::vector<Segment> mapactive;
  static std::vector<Segment> map;
  static double lasttime = -1;;

  static Vector2 curpos(0,.5);
  static float angle = 0;
  static float begintime = -1;


  if (g_mapIteration) {
    
    if (rand() % 2 == 0) {
      angle = 3.1415926535 / 4;
    }
    else {
      angle = 0;
    }
    if (rand()&1)
      angle *=-1;

    //    if (rand()&1)
    //      angle *=2;

    if (mapactive.size()>0) {
      int segnr = rand()%mapactive.size();
      Segment s = mapactive[segnr];
      mapactive.erase( mapactive.begin()+segnr );
      curpos = s.position;
      Vector2 direction(.1, 0);
      direction = direction.rotate(s.angle);
      
      curpos.x += direction.x;
      curpos.y += direction.y;
      angle+=s.angle;



      if (curpos.x < 0)
        curpos.x = .5;

      if (curpos.y < 0)
        curpos.y = .5;

      if (curpos.y > 1)
        curpos.y = .5;

      if (curpos.x > 1)
        curpos.x = .5;

      //      curpos.x = std::fmod(curpos.x, 1);
      //      curpos.y = std::fmod(curpos.y, 1);


      
    }
    Segment seg(curpos.x, curpos.y, angle);
    
    Vector2 direction(.1, 0);
    direction = direction.rotate(angle);

    curpos.x += direction.x;
    curpos.y += direction.y;
    //curx += 0.1;

    map.push_back(seg);
    mapactive.push_back(seg);
    if (rand()%7 ==0) {
      mapactive.push_back(seg);
    }
    lasttime = time;
      
  }
  // draw entire map

  int mapsize = map.size();

  for (int i = 0; i < mapsize; ++i) {
    drawSegment(0, map[i]);

  }

  
  if (order != lastOrder) {
    map.clear();
    mapactive.clear();
    curpos.x = .5;
    curpos.y = .5;
    lastOrder= order;
  }



  count--;
  if (count == 0) {
    g_mapIteration = false;
    count = 20;
  }

  glDisable(GL_POLYGON_STIPPLE);

}


void drawSections(double time, int order) {
  
  static int lastOrder = -1;
  static float xc = .5;
  static float yc = .5;
  static double lastTime = -1;


  double delta = (time-lastTime);



  if (lastOrder != order) {
    
    lastOrder = order;
    xc = (float)std::rand() / (float)RAND_MAX;
    yc = (float)std::rand() / (float)RAND_MAX;
    xc = xc * .5 + .25;
    yc = yc * .5 + .25;
  }






  static unsigned int polyStipple0[32*4];
  for (int y = 0; y < 32; y++) {
    polyStipple0[31-y] = (1<<(y&3))*0x11111111;
  }
  glPolygonStipple((GLubyte*)polyStipple0);
  //  glEnable(GL_POLYGON_STIPPLE);
  //glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
  glBlendFunc(GL_ONE, GL_ONE);
  glEnable(GL_BLEND);
  glBegin(GL_QUADS);
  
  float o = g_sectionRadius * 0.025;

  
  float off = 2.0*3.1415926535 / 6.0f;
  glColor4f(cos(time)*.5+.5 ,0,0,.5);
  for (int i = 0; i < 6; ++i) {
    drawFilledRing(o+ .15, o+.2, xc, yc, 40, time+i*off, time+i*off+off*.95);
  }
  glColor4f(cos(time*1.34)*.5+.5 ,0,0,.5);
  for (int i = 0; i < 6; ++i) {
    drawFilledRing(o+.13,o+.14, xc, yc, 40, 1.32*time+i*off, 1.32*time+i*off+off*.95);
  }
  glColor4f(cos(time*0.34)*.5+.5 ,0,0,.5);
  for (int i = 0; i < 6; ++i) {
    drawFilledRing(o+.205,o+.21, xc, yc, 40, -1.43*time+i*off, -1.43*time+i*off+off*.95);
  }
  glColor4f(cos(time*1.41)*.5+.5 ,0,0,.5);
  for (int i = 0; i < 6; ++i) {
    drawFilledRing(o+.245,o+.26, xc, yc, 40,  1.31*time+i*off, 1.31*time+i*off+off*.95);
  }
  glColor4f(cos(time*1.37)*.5+.5 ,0,0,.5);
  for (int i = 0; i < 6; ++i) {
    drawFilledRing(o+.22, o+.225, xc, yc, 40, -1.48*time+i*off, -1.48*time+i*off+off*.95);
  }
  glColor4f(cos(time*1.51)*.5+.5 ,0,0,.5);
  for (int i = 0; i < 6; ++i) {
    drawFilledRing(o+.23,o+.24, xc, yc, 40, 1.21*time+i*off, 1.21*time+i*off+off*.95);
  }
  glColor4f(cos(time*1.51)*.5+.5 ,0,0,.5);
  for (int i = 0; i < 6; ++i) {
    drawFilledRing(o+.27,o+.6, xc, yc, 40, 1.21*time+i*off, 1.21*time+i*off+off*.95);
  }  

  
  glEnd();
  glDisable(GL_BLEND);
  glDisable(GL_POLYGON_STIPPLE);
  g_sectionRadius *= std::pow(0.01, delta);
  lastTime= time;

  
}


void drawQuads(double time) {
  glBegin(GL_QUADS);
  float quadWidth = .05;

  for (int j = 0; j < 10; ++j) {
    float yoff = sin(j*0.2542) * 0.2;;
    double t = std::fmod(time + j * .15, 1.3) - .3;
    glColor3f(0, 0, 0);
    glVertex2f(t, 0.5 + yoff);
    glVertex2f(t, 0.6 + yoff);
    glVertex2f(quadWidth+t, 0.6 + yoff);
    glVertex2f(quadWidth+t, 0.5 + yoff);
  }
  glEnd();
}

void drawTexturedQuad(float width, float height, float xpos, float ypos, int texture, float rotation = 0) {

  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, g_textures[texture]);
  //  glColor3f(1,1,1);
  //  glDisable(GL_BLEND);
  
  glBegin(GL_QUADS);


  Vector2 a(-width/2,  -height/2);
  Vector2 b(width/2,  -height/2);
  Vector2 c(width/2,  height/2);
  Vector2 d(-width/2,  height/2);

  a = a.rotate(rotation);
  b = b.rotate(rotation);
  c = c.rotate(rotation);
  d = d.rotate(rotation);

  Vector2 center(xpos, ypos);
  a = a + center;
  b = b + center;
  c = c + center;
  d = d + center;
  


  glTexCoord2f(0, 0);
  a.gl();
  glTexCoord2f(1, 0);
  b.gl();
  glTexCoord2f(1, 1);
  c.gl();
  glTexCoord2f(0, 1);
  d.gl();

  glEnd();
  glDisable(GL_TEXTURE_2D);
}

std::vector<unsigned char> randoms;
int rnduser=0;
float thetime=0;

void drawTriangleRec( Vector2 a, Vector2 b, Vector2 c, int depth=0 )
{
  int col = depth&1;
  if (col) 
    glEnable(GL_POLYGON_STIPPLE);


  glBegin(GL_TRIANGLES);
  glColor4f(std::cos(depth+g_rndUser+g_sectionRadius) + g_sectionRadius*.3 ,0,0,0.5);
  a.gl();
  b.gl();
  c.gl();
  glEnd();
  
  if (depth<=5)
    {
      unsigned char r =  randoms[rnduser++];
      if (depth<=2)
        while(r&7 == 0)
          r =  randoms[g_rndUser++];
      
      double f=0.5+0.3*sin(g_sectionRadius);
      if (r&1)
        drawTriangleRec( a, (b-a)*f+a, (c-a)*f+a, depth+1 );
      if (r&2)
        drawTriangleRec( b, (c-b)*f+b, (a-b)*f+b, depth+1 );
      if (r&4)
        drawTriangleRec( c, (a-c)*f+c, (b-c)*f+c, depth+1 );
      //   if (r&8)
      //       drawTriangleRec( , depth+1 );
    }

  glEnable(GL_LINE_SMOOTH);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
  glBegin(GL_LINES);
  glColor3f(0,0,0);
  a.gl(); b.gl();
  b.gl(); c.gl();
  c.gl(); a.gl();
  glEnd();
  glDisable(GL_LINE_SMOOTH);
  glDisable(GL_BLEND);
  glDisable(GL_POLYGON_STIPPLE);
}

void drawTriangle(double time) {
  static double lastTime = -1;

  double delta = time - lastTime;


  //  g_sectionRadius *= 0.99;
  unsigned int pattern[32*4];
  for (int y = 0; y < 32; y++) {
    pattern[31-y] = (1<<(y&3))*0x11111111;
  }

  glPolygonStipple((GLubyte*)pattern);
        
  Color outside(0,0,0);
  //  glEnable(GL_POLYGON_STIPPLE);

  static bool once=true;
  thetime = time;
  if (once)
    {
      once = false;
      for(int i=0;i<100000;i++)
        randoms.push_back(rand()  );
    }
  //  glEnable(GL_BLEND);
  //  glBlendFunc(GL_ONE_MINUS_SRC_COLOR,GL_SRC_COLOR);

  // glBegin(GL_TRIANGLES);
  
  // glColor4f(0.4,0.2,0.5,1 );
  rnduser = g_rndUser;
  drawTriangleRec( Vector2( .5,.2 ),  Vector2( 1,.8 ), Vector2( 0,.8 ) );
  //  glEnd();
  glDisable(GL_POLYGON_STIPPLE);

  glEnable(GL_BLEND);
  glBlendFunc(GL_ONE_MINUS_SRC_COLOR,GL_SRC_COLOR);
  
  
  float o = g_sectionRadius * .1;
  //float o =0;
  
  // moo2
  for (int i = 0; i < 4; ++i ) {
    float o = std::fmod(time*.3 + i*.2, 1.4)  *.4; //cos(time+i)*.2+.2;
    glColor3f(1,1,1);
    float scale = .15*o;
    float scale2 = .075 - scale;
    if (scale2 < 0)
      scale2 = 0;
    drawTexturedQuad(scale, scale, .5+o, .2+o*1.2, 6, -3.1415/4.0);
    drawTexturedQuad(scale2, scale2,    o, .8-o*1.2, 6,  3.1415/4.0);
  }

  glDisable(GL_BLEND);

  lastTime = time;
  g_sectionRadius *= std::pow(0.01, delta);


}


void drawquadedge( Vector2 a, Vector2 b, int depth=0 )
{
  
  if (depth<=5)
    {
      unsigned char r =  randoms[rnduser++];
      if (r&7)
        {
          // subdiv
          Vector2 p1,p2,p3;
          /*

          a--------------b

          becomes:

          p1----p2
          |       |
          a       p3----b

          */
          
          float m = sin(thetime+depth+r);

          p1 = a+(b-a).rotate(-M_PI*0.5 +m)*0.5;
          p2 = p1 + (b-a)*0.5;
          p3 = (a+b)*0.5;

          if (std::abs(m) < .2) {
            glEnable(GL_POLYGON_STIPPLE);
          }
          
          if (depth>1)
            {
              glBegin(GL_QUADS);
              glColor3f(depth*0.2,0,0);
              //glColor3f(1,0,0);
              a.gl();
              p1.gl();
              p2.gl();
              p3.gl();
              glEnd();

              if (depth == 2 && std::abs(m) < .2) {
                //    glColor3f(0,1,0);
                //                glBegin(GL_POINTS);
                //                glVertex2f((p1.x+p3.x)/2, (p1.y+p3.y) / 2);

                float cx = (p1.x+p3.x)/2;
                float cy = (p1.y+p3.y)/2;


                //                glEnd();
                glBegin(GL_LINES);
                glColor3f(0,0,0);
                glVertex2f(cx-0.01, cy);
                glVertex2f(cx+0.01, cy);
                glVertex2f(cx, cy-0.01);
                glVertex2f(cx, cy+0.01);
                
                //a.gl();
                glEnd();

              }


            }

          drawquadedge( a,p1 , depth+1) ;
          drawquadedge( p1,p2, depth+1 ) ;
          drawquadedge( p2,p3, depth+1 ) ;
          drawquadedge( p3,b , depth+1) ;
          return;
        }
    }

  if (depth == 3) {
    glBegin(GL_LINES);
    glColor3f(1,1,1);
    glVertex2f(a.x-0.01, a.y);
    glVertex2f(a.x+0.01, a.y);
    glVertex2f(a.x, a.y-0.01);
    glVertex2f(a.x, a.y+0.01);


    //a.gl();
    glEnd();
  }
  glDisable(GL_POLYGON_STIPPLE);
  //  glBegin(GL_LINES);
  //  a.gl();
  //  b.gl();
  //  glEnd();
}

void drawMagicQuad(double time) {

  static unsigned int polyStipple0[32*4];
  for (int y = 0; y < 32; y++) {
    polyStipple0[31-y] = (1<<(y&3))*0x11111111;
  }
  glPolygonStipple((GLubyte*)polyStipple0);


  static bool once=true;
  thetime = time;
  if (once)
    {
      once = false;
      for(int i=0;i<100000;i++)
        randoms.push_back(rand()  );
    }

  rnduser = g_rndUser;
  //  rnduser = time;
  drawquadedge( Vector2( .2,.2 ),  Vector2( .8,.2 ) );
  drawquadedge( Vector2( .8,.2 ),  Vector2( .8,.8 ) );
  drawquadedge( Vector2( .8,.8 ),  Vector2( .2,.8 ) );
  drawquadedge( Vector2( .2,.8 ),  Vector2( .2,.2 ) );
  //  glEnd();
  glDisable(GL_POLYGON_STIPPLE);
}



float getTime() {
  return SDL_GetTicks() / 1000.0f;
}


void load() {
  
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0,1,1.0 - 1.0/8.0,1.0/8.0,0.1,1000);
  glTranslatef(0,0,-70);

  float colors[] = {.5,0,.5,
                    0,0,.5,
                    0,0,1,
                    0,1,0,
                    1,1,0,
                    1,.5,0,
                    1,0,0};


  bool quit = false;
  SDL_Event event;
  float thickness = 0.025;
  while (!quit) {
 
    while(SDL_PollEvent(&event)){ 
      switch(event.type){ 
      case SDL_KEYDOWN:  
        quit = true;
        break;
      }
    }
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    for (int i = 0; i < 7; ++i) {
      glColor3f(colors[3*i],colors[3*i+1],colors[3*i+2]);
      glBegin(GL_QUADS);
      drawFilledRing(.2+i*thickness,.2+thickness+i*thickness, .5, .65, 20, 0,-getTime());
      glEnd();
    }
    SDL_GL_SwapBuffers();    
    if (getTime() >= 3.141516)
      break;
  }


}

int main(int argc, char *argv[]) {
  

  SDL_Init(SDL_INIT_VIDEO);
  SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
  SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
  SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  SDL_Surface *screen = 0;
  if ((screen=SDL_SetVideoMode( 640, 480, 0, SDL_OPENGL | SDL_FULLSCREEN)) == 0) {
    fprintf(stderr, "Couldn't set GL mode: %s\n", SDL_GetError());
    SDL_Quit();
    return 0;
  }
#ifdef _MSC_VER
  ShowCursor(0);
#endif

  bool quit = false;
  SDL_Event event;

  load();

  g_textures[0] = loadTextureFromFile("paintart1.PNG");
  g_textures[1] = loadTextureFromFile("paintart2.PNG");
  g_textures[2] = loadTextureFromFile("paintart3.PNG"); 
  g_textures[3] = loadTextureFromFile("paintart4.png"); 
  g_textures[4] = loadTextureFromFile("paintart5.png"); 
  g_textures[5] = loadTextureFromFile("paintart6.PNG"); 
  g_textures[6] = loadTextureFromFile("cow.PNG"); 


  FMUSIC_MODULE *mod = NULL;
  
  if (FSOUND_GetVersion() < FMOD_VERSION) {
    
    printf("Error : You are using the wrong DLL version!  You should be using FMOD %.02f\n", FMOD_VERSION);
    exit(1);
  }
  
  if (!FSOUND_Init(32000, 64, 0)) {
    printf("%s\n", FMOD_ErrorString(FSOUND_GetError()));
    exit(1);
  }

  mod = FMUSIC_LoadSong("bp_demo_boemtjak.xm");
  if (!mod) {
    printf("%s\n", FMOD_ErrorString(FSOUND_GetError()));
    exit(1);
  }
  FMUSIC_PlaySong(mod);     

  for (int i = 0;i <30;++i)
    FMUSIC_SetInstCallback(mod, 
                           instCallback,
                           i);


  if (argc == 2) {
    FMUSIC_SetOrder(mod, atoi(argv[1]));
    
  }




  int mask = 0;
  while (!quit) {
    while(SDL_PollEvent(&event)){ 
      switch(event.type){ 
      case SDL_KEYDOWN:  
        quit = true;
        break;
      }
    }

    int pattern = FMUSIC_GetOrder(mod);
    int row = FMUSIC_GetRow(mod);

    float time = getTime();


    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0,1,1.0 - 1.0/8.0,1.0/8.0,0.1,1000);
    glTranslatef(0,0,-70);


    ///    {
    //     
    //      SDL_GL_SwapBuffers();
    //      continue;
    //    }




    if (pattern >3) {

      if (1) {


        Color a(.1, .1, .1);
        Color a2(.5, .5, .5);
        Color b(0,0,0);




        //      if (mask&16)
        //      drawSun(time);    

        if (mask&1 && pattern < 12 && pattern != 7)
          drawLines(time);
        if (mask&2)
          drawQuads(time);
      
        if (mask&4)
          drawHypnoQuads(time);

      
        //      if (pattern >= 12)
        //        drawCircles(time);

      
        if (pattern <7)
          drawHexagons(time);
           
        drawMetroMap(time, pattern);
        if (pattern >= 12 && pattern < 16)
          drawSections(time, pattern);


        if (pattern >= 12) {


          /*
            for (int i = 0; i < 100; ++i) {
            float x = std::fmod(time*cos(i) + i*.2, 1);
            float y = (cos((float)i*1.3298)) * .2 + .5;
            drawShadedCircle(.01, a, a, x , y, true);
            glDisable(GL_BLEND);
            glBegin(GL_QUADS);
            glEnd();
            }
          */

        }
      }

    }
    else {
      drawFlowers(time);
      drawHypnoQuads(time);
      drawCrosses(time);
    }


    
    if (pattern > 3 && pattern < 7) {
      static float xp=.5;
      static float yp=.7;
      static int textureIdx = 0;
      static double lastTime = -1;


      if (g_randomizeText) {
        g_randomizeText= false;
        xp = ((float) rand() / (float) RAND_MAX) * .5 + .25;
        textureIdx++;
      }

      glColor3f(1,1,1);
      drawTexturedQuad(.4,.1,xp,yp, 3+textureIdx%3);

      glEnable(GL_BLEND);
      glBlendFunc(GL_ONE_MINUS_SRC_COLOR,GL_SRC_COLOR);

      float o = g_sectionRadius * .1;
      glColor3f(1,1,1);
      drawTexturedQuad(.4+o,.4+o, xp, yp-.1, 6);
      glDisable(GL_BLEND);

      

      float c = g_cowbell;
      glColor4f(c,c*.7,c*.5,1);
      glEnable(GL_BLEND);
      glBlendFunc(GL_ONE, GL_ONE);
      float r = .02 - g_cowbell * .02;

      glBegin(GL_LINES);
      drawCircle(r+.02, xp+.175, yp-.2, 20, 0);
      drawCircle(r+.03, xp+.175, yp-.2, 20, 0);
      drawCircle(r+.04, xp+.175, yp-.2, 20, 0);
      glDisable(GL_BLEND);
      glEnd();

      double delta = time- lastTime;

      g_cowbell *= std::pow(0.001, delta);
      g_sectionRadius*=std::pow(0.01, delta);

      lastTime = time;


    }

    if (pattern == 7){

      static unsigned int polyStipple0[32*4];
      for (int y = 0; y < 32; y++) {
        polyStipple0[31-y] = (1<<(y&3))*0x11111111;
      }
      glPolygonStipple((GLubyte*)polyStipple0);



      

      // moooooooh
      static float xp=.5;
      static float yp=.7;
      static int textureIdx = 0;
      static float lastTime = -1;


      if (g_randomizeText) {
        g_randomizeText= false;
        xp = ((float) rand() / (float) RAND_MAX) * .5 + .25;
        textureIdx++;
      }

      for (int i = 0 ; i < 5; ++i) {
        for (int j =0; j < 5; ++j) {
          if (i+j == g_cowSolution%10)
            continue;

          float xp = (i / 3.8f);
          float yp = j / 6.0f;

          xp = std::fmod(xp + time*.3, 1.3) - .15 ;
          yp = std::fmod(yp, 1) + .2;

          glEnable(GL_BLEND);
          glBlendFunc(GL_ONE_MINUS_SRC_COLOR,GL_SRC_COLOR);

          if (i*1.3+j*1.5 == g_cowSolution%10)
            glEnable(GL_POLYGON_STIPPLE);

          float o = g_sectionRadius * .1;

          glColor3f(1,0,0);
          drawTexturedQuad(.15+o,.15+o, xp, yp-.1, 6);
          drawTexturedQuad(.15,.15, xp, yp-.1, 6);
          glDisable(GL_BLEND);

          float c = g_cowbell;
          glColor4f(c,c*.7,c*.5,1);
          glEnable(GL_BLEND);
          glBlendFunc(GL_ONE, GL_ONE);
          float r = .01 - g_cowbell * .01;

          if ((i*j)%5 == g_cowBellSolution%5) {

            glBegin(GL_LINES);
            drawCircle(r+.01*.5, xp+.072, yp-.125, 20, 0);
            drawCircle(r+.04*.5, xp+.072, yp-.125, 20, 0);
            drawCircle(r+.08*.5, xp+.072, yp-.125, 20, 0);
            glDisable(GL_BLEND);
            glEnd();
          }


          double delta = time - lastTime;
          g_cowbell *= std::pow(0.0001, delta);
          g_sectionRadius*=std::pow(0.01, delta);
          lastTime = time;


          glDisable(GL_POLYGON_STIPPLE);

        }
      }
    
      glColor3f(1,1,1);
      drawTexturedQuad(.4, .1, .3, .5, 0);
    }
  

    if (pattern >= 8 && pattern < 12) {
      drawTriangle(time);
      glColor3f(1,1,1);
      drawTexturedQuad(.4, .1, .3, .5, 5);
    }

    if (pattern >= 16) 
      drawMagicQuad(time);
    
    if (mask&8 && pattern >= 4)
      drawCrosses(time);    


    if (pattern >= 12) {
      glColor3f(1,1,1);
      drawTexturedQuad(.4,.1,.5,.7,1);
    }

    if (std::rand() % 100 == 5)
      mask+= rand()&0xf;

    SDL_GL_SwapBuffers();

    if (pattern == 20 && row == 2) {
      break;
    }

  }

  SDL_Quit();

  return 0;


}


