// ifs1.cpp
// by Glenn G. Chappell
// April 2004
// Based on IFS fractal code by Chris Hartman
//
// For CS 481/681
// Fractal Generation using Iterated Function Systems


#include <ctime>
#ifndef _MSC_VER  // Stupic Microsoft cstdlib & ctime files are broken!!
using std::rand;
using std::srand;
using std::time;
#endif
#include <iostream>
using std::cerr;
using std::endl;
#include <string>
using std::string;
#include <sstream>
#include <stdlib.h>
//using std::exit;
#include <GL/glut.h> // GLUT stuff - includes OpenGL headers
#include <fstream>
#include <vector>


// Global variables
// Window/viewport
const int startwinsize = 400; // Starting window width & height, in pixels
int winw, winh;               // Window size

// Keyboard
const int ESCKEY = 27;        // ASCII value of escape character

// For IFS fractals
// - Drawing state
const int ptsperdisp = 1000;  // How many points to draw each display func call
bool bigpoints = false;       // True if points to be drawn large
bool equalprobs = false;      // True if maps are to be chosen with equal probabilities
bool colorme = false;         // If true, color according to latest map used
bool showhelp = true;         // True if text instructions shown
int currfrac;                 // Subscript of current IFS
double x, y;                  // Current point in IFS
bool newfrac;                 // True if no points yet drawn for this IFS

// - Storage for IFS
struct ifs_s  // Structure to hold description on an IFS
{
   std::string name;       // Name of IFS
   double left;            // Viewport spec's
   double bottom;
   double side;
   int nummaps;            // Number of maps in this IFS
   std::vector<double> a;  // Entries of 2x2 matrices for maps
   std::vector<double> b;
   std::vector<double> c;
   std::vector<double> d;
   std::vector<double> s;  // Coord's for translate vectors of maps
   std::vector<double> t;
   std::vector<double> p;  // Probability of using each map
};
std::vector<ifs_s> ifs;  // Array of IFS's
int numfracs;    // Number of IFS's available

// - Color constants
const int NUMCOLORS = 8;
const GLubyte colors[8][3] = {  // Colors to use when coloring by latest map
   {0xff,0x90,0x00},
   {0x00,0x90,0xff},
   {0x99,0x00,0x99},
   {0x00,0xff,0x99},
   {0xff,0x24,0x58},
   {0x90,0xff,0x00},
   {0x90,0x00,0xff},
   {0x90,0x90,0x00}};

const GLubyte maincolor[3] = { 0x40, 0x00, 0x40 };  // Std color for points

// - Input file
const string ifsfilename = "ifsdat.txt";


// tostring
// Convert argument to string class using operator<<
// Must include <sstream>
template<typename T>
std::string tostring(const T & input)
{
   std::ostringstream os;
   os << input;
   return os.str();
}


// printbitmap
// Prints the given string at the given raster position
//  using GLUT bitmap fonts.
// You probably don't want any rotations in the model/view
//  transformation when calling this function.
void printbitmap(const string msg, double x, double y)
{
   glRasterPos2d(x, y);
   for (string::const_iterator ii = msg.begin();
        ii != msg.end();
        ++ii)
   {
      glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *ii);
   }
}


// iterate
// If newfrac, generate a random point.
// Else apply a randomly-chosen map from current IFS to (x,y)
// Set up color for next point
void iterate()
{
   if (newfrac)
   {
      x = static_cast<double>(rand())/(1+RAND_MAX) * ifs[currfrac].side + ifs[currfrac].left;
      y = static_cast<double>(rand())/(1+RAND_MAX) * ifs[currfrac].side + ifs[currfrac].bottom;
      glColor3ubv(maincolor);
      newfrac = false;
      return;
   }

   int whichmap;
   if (equalprobs)
   {
      whichmap = static_cast<double>(rand())/(1+RAND_MAX)*ifs[currfrac].nummaps;
   }
   else
   {
      double randval = static_cast<double>(rand()) / (1+RAND_MAX);
      for (int i=0; i<ifs[currfrac].nummaps; ++i)
      {
         if (randval < ifs[currfrac].p[i]) break;
         randval -= ifs[currfrac].p[i];
      }
      whichmap = i;
   }

   double newx = ifs[currfrac].a[whichmap] * x + ifs[currfrac].b[whichmap] * y + ifs[currfrac].s[whichmap];
   y =           ifs[currfrac].c[whichmap] * x + ifs[currfrac].d[whichmap] * y + ifs[currfrac].t[whichmap];
   x = newx;

   if (colorme && whichmap < NUMCOLORS)
      glColor3ubv(colors[whichmap]);
   else
      glColor3ubv(maincolor);
}


// setfrac
// Change to a new fractals or restart current fractal
void setfrac(int fracnum)
{
   currfrac = fracnum;
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   double halfside = ifs[currfrac].side / 2.0;
   double xc = ifs[currfrac].left + halfside;
   double yc = ifs[currfrac].bottom + halfside;
   if (winw > winh)
   {
      glOrtho(xc-halfside*winw/winh,xc+halfside*winw/winh, yc-halfside,yc+halfside, -1.0,1.0);
   }
   else
   {
      glOrtho(xc-halfside,xc+halfside, yc-halfside*winh/winw,yc+halfside*winh/winw, -1.0,1.0);
   }
   glMatrixMode(GL_MODELVIEW);

   newfrac = true;

   std::string winnamehead = "CS 381 - IFS Fractals - ";
   glutSetWindowTitle((winnamehead + ifs[currfrac].name).c_str());
}


// readfile
// Reads IFS descriptions from text file ifsdat.txt
// Returns false if error
bool readfile(const string filename)
{
   std::ifstream data(filename.c_str());
   if (!data) return false;
   numfracs = 0;
   while (!data.eof())
   {
      ifs_s currifs;
      bool fileend = false;
      while(true)
      {
         if (!std::getline(data, currifs.name, '\n'))
         {
            fileend = true;
            break;
         }
         int pos = int(currifs.name.find_last_not_of(" \t\n\r"));
         if (pos == std::string::npos) continue;
         else
         {
            currifs.name.resize(pos+1);
            break;
         }
      }
      if (fileend) break;

      data >> currifs.left;
      data >> currifs.bottom;
      data >> currifs.side;
      data >> currifs.nummaps;
      if (currifs.nummaps < 1) break;
      currifs.a.resize(currifs.nummaps);
      currifs.b.resize(currifs.nummaps);
      currifs.c.resize(currifs.nummaps);
      currifs.d.resize(currifs.nummaps);
      currifs.s.resize(currifs.nummaps);
      currifs.t.resize(currifs.nummaps);
      currifs.p.resize(currifs.nummaps);
      double totalp = 0.0;
      for (int i=0; i<currifs.nummaps; ++i)
      {
         data >> currifs.a[i];
         data >> currifs.b[i];
         data >> currifs.c[i];
         data >> currifs.d[i];
         data >> currifs.s[i];
         data >> currifs.t[i];
         double p = currifs.a[i]*currifs.d[i]-currifs.b[i]*currifs.c[i];
         if (p < 0.0) p = -p;
         if (p < 0.01) p = 0.01;
         currifs.p[i] = p;
         totalp += p;
      }
      for (int j=0; j<currifs.nummaps; ++j)
      {
         currifs.p[j] /= totalp;
      }
      ifs.push_back(currifs);
      ++numfracs;
      while(data.get()!='\n' && !data.eof()) ;
   }
   return true;
}


// display
// The GLUT display function
void display()
{
   // Draw fractal
   if (newfrac)  // newfrac flag is cleared in iterate()
      glClear(GL_COLOR_BUFFER_BIT);
    
   if (bigpoints)
      glPointSize(5.0);
   else
      glPointSize(1.0);

   glBegin(GL_POINTS);
      for (int i=0; i<ptsperdisp; ++i)
      {
         iterate();
         glVertex2d(x, y);
      }
   glEnd();

   // Draw instructions
   if (showhelp)
   {
      glMatrixMode(GL_PROJECTION);
      glPushMatrix();
         glLoadIdentity();
         gluOrtho2D(-1., 1., -1., 1.);  // Old faithful
         glColor3d(1., 0.1, 0.1);
         printbitmap("IFS Fractals", -0.9, 0.9);
         printbitmap("<- -> Switch fractal [" + tostring(1+currfrac) + "/" + tostring(numfracs) + "]", -0.9, 0.8);
         printbitmap("SPACE Restart", -0.9, 0.7);
         printbitmap("ESC   Exit", -0.9, 0.6);
         printbitmap("Toggles:", -0.9, 0.5);
         printbitmap(string("B  Big points [") + (bigpoints ? "X" : " ") + "]", -0.9, 0.4);
         printbitmap(string("P  Equal probabilities [") + (equalprobs ? "X" : " ") + "]", -0.9, 0.3);
         printbitmap(string("C  Color by last map [") + (colorme ? "X" : " ") + "]", -0.9, 0.2);
         printbitmap("H  Help", -0.9, 0.1);
      glPopMatrix();
      glMatrixMode(GL_MODELVIEW);
   }

   glFlush();  // Single-buffered!
}


// keyboard
// The GLUT keyboard function
void keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case 27:  // Escape key
      exit(0);
      break;
   case ' ':
      setfrac(currfrac);
      break;
   case 'c':
   case 'C':
      colorme = !colorme;
      setfrac(currfrac);
      break;
   case 'b':
   case 'B':
      bigpoints = !bigpoints;
      setfrac(currfrac);
      break;
   case 'p':
   case 'P':
      equalprobs = !equalprobs;
      setfrac(currfrac);
      break;
   case 'h':
   case 'H':
      showhelp = !showhelp;
      setfrac(currfrac);
      break;
   }
}


// special
// The GLUT special function
void special(int key, int x, int y)
{
   switch (key)
   {
   case GLUT_KEY_RIGHT:
      ++currfrac;
      if (currfrac >= numfracs) currfrac = numfracs-1;
      setfrac(currfrac);
      break;
   case GLUT_KEY_LEFT:
      --currfrac;
      if (currfrac < 0) currfrac = 0;
      setfrac(currfrac);
      break;
   }
}


// reshape
// The GLUT reshape function
void reshape(int w, int h)
{
   glViewport(0, 0, w, h);
   winw = w;
   winh = h;

   setfrac(currfrac);

   glLoadIdentity();  // Modelview transformation
}


// idle
// The GLUT idle function
void idle()
{
   if (GLenum err = glGetError())
   {
      cerr << "OpenGL ERROR: " << gluErrorString(err) << endl;
   }

   glutPostRedisplay();
}


// init
// Initialization
// Called by main after window creation.
void init() 
{
   srand(time(0));
   glClearColor(1.0, 1.0, 1.0, 0.0);  // white background
   currfrac = 0;

   if (!readfile(ifsfilename))
   {
      cerr << "Could not read file: " << ifsfilename << endl;
      exit(1);
   }
}


int main(int argc, char ** argv)
{
   // Initialize OpenGL/GLUT
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);

   // Make a window
   glutInitWindowSize(startwinsize, startwinsize);
   glutInitWindowPosition(50, 50);
   glutCreateWindow("CS 481/681 - Particles");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutSpecialFunc(special);
   glutIdleFunc(idle);

   // Do something
   glutMainLoop();

   return 0;
}
