// Flip and twist final program by Mark Hibbard
#include <stdio.h>
#include <stdio.h>
#include <iostream.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "glaux.h"
#include <fstream.h>
typedef struct {
   char id_len;                 // ID Field (Number of bytes - max 255)
   char map_type;               // Colormap Field (0 or 1)
   char img_type;               // Image Type (7 options - color vs. compression)
   int  map_first;              // Color Map stuff - first entry index
   int  map_len;                // Color Map stuff - total entries in file
   char map_entry_size;         // Color Map stuff - number of bits per entry
   int  x;                      // X-coordinate of origin 
   int  y;                      // Y-coordinate of origin
   int  width;                  // Width in Pixels
   int  height;                 // Height in Pixels
   char bpp;                    // Number of bits per pixel
   char misc;                   // Other stuff - scan origin and alpha bits
} targa_header;

int res = 0;
double px, py;

   unsigned char buffR[1024 * 1024];
   unsigned char buffG[1024 * 1024];
   unsigned char buffB[1024 * 1024];
   unsigned char buffBGR[1024 * 1024 * 3];




// An Evil GLOBAL Structure Variable 
// This will hold essential statistics used during the display loop
struct windowStats
   {double x1,    // First Point
	   y1,
	   x2,    // Second Point
	   y2;
   } base;
class scale
  {
    private:
      double flx, fly, frx, fuy, tlx, tly, trx, tuy, x, y;
    public:
      void SetSourceScale(float lx, float ly, float rx,float uy)
           { flx = lx; fly = ly; frx = rx; fuy = uy; }
      void SetDestScale(float lx, float ly, float rx, float uy)
           { tlx = lx; tly = ly; trx = rx; tuy = uy; }
      void SetPoint(float sx, float sy) {x = sx; y = sy;}
      double GetX(void) 
           {return(((x - flx) * (trx - tlx) / (frx - flx)) + tlx);}
      double GetY(void)
           {return(((y - fly) * (tuy - tly) / (fuy - fly)) + tly);}
} wnd;

// A GLOBAL Class for Points.  This could be used for the same purposes
// as the above structure  
class myPoint
  { 
   private:       // Coordinate Positions for X and Y
     double x,y;

   public:
     myPoint ()   // Constructor to Initialize values
       {x = 0.0; y = 0.0;}

     void assign (float a, float b )   // A way to assign new values. 
       {x = (double) a; y = (double) b;}

     double getX (void)    // The method to access the X-value
       {return x;}

     double getY (void)    // The method to access the Y-value
       {return y;}

     double distance (myPoint);  // Prototype of function 

     void getPoint (void);       // Prototype of function

  }A,B, WndSize;    // End of Class definition, and Two Global Objects of that type.

int changed;
int cancel;


void myPoint :: getPoint (void)    // Interactive function for initialization 
  {
   cout << "Enter x coordinate: ";
   cin >> x;
   cout << "Enter y coordinate: ";
   cin >> y;
  }
   

double myPoint:: distance(myPoint P) // Function to find distance from
  {                                  // another point
    double d;
    d = (double) sqrt( ( P.getX() - x)*(P.getX() - x) + 
                        (P.getY() - y) * (P.getY() - y));
    return d;
  }

// This Section creates a Window, but uses only GL commands rather than X.

static void Init( void )
{
   /* one-time init (clearColor, set palette, etc) */

   glClearColor(0.5, 0.5, 0.5, 1.0 );
   glShadeModel( GL_FLAT );
}

/* Function to write the header information to the file */
void writeheader(targa_header h, ofstream &tga) 
  {
   char lo, hi;
   tga.write((char *) &(h.id_len), 1);          // Write chars for ID, map, and image type
   tga.write((char *) &(h.map_type), 1);
   tga.write((char *) &(h.img_type), 1);
   lo = h.map_first % 256;
   hi = h.map_first / 256;
   tga.write((char *) &lo, 1); tga.write((char *) &hi, 1);
   lo = h.map_len % 256; hi = h.map_len / 256;
   tga.write((char *) &lo, 1); tga.write((char *) &hi, 1);
   tga.write((char *) &(h.map_entry_size), 1);  // Write a char - only one byte
   lo = h.x % 256; hi = h.x / 256;
   tga.write((char *) &lo, 1); tga.write((char *) &hi, 1);
   lo = h.y % 256; hi = h.y / 256;
   tga.write((char *) &lo, 1); tga.write((char *) &hi, 1);
   lo = h.width % 256; hi = h.width / 256;
   tga.write((char *) &lo, 1); tga.write((char *) &hi, 1);
   lo = h.height % 256; hi = h.height / 256;
   tga.write((char *) &lo, 1); tga.write((char *) &hi, 1);
   tga.write((char *) &(h.bpp), 1);             // Write two chars
   tga.write((char *) &(h.misc), 1);
  }/* pts.cpp */

int writetargafile(void) {
   targa_header header;     // Variable of targa_header type
   int x, y;

/* First, set all the fields in the header to appropriate values */
   header.id_len = 0;          /* no ID field */
   header.map_type = 0;        /* no colormap */
   header.img_type = 2;        /* trust me */
   header.map_first = 0;       /* not used */
   header.map_len = 0;         /* not used */
   header.map_entry_size = 0;  /* not used */
   header.x = 0;               /* image starts at (0,0) */
   header.y = 0;
   header.width = (int) WndSize.getX();         /* image is 256x128 */
   header.height = (int) WndSize.getY();
   header.bpp = 24;            /* 24 bits per pixel */
   header.misc = 0x20;         /* scan from upper left corner */

/* Open a file for writing targa data.  Call the file "test.tga and
      write in binary mode (wb) so that nothing is lost as characters
      are written to the file */
   ofstream tga("test.tga"); /* Write the header information  */
   writeheader(header, tga);  
/* for each pixel on the screen, give the blue, then green, then red
   components.  I know this order seems backwards, but that's how it goes.
   Cycle through blue, green, and red components in order to generate
   a rainbow effect.  */ 
   unsigned char buffR[1024 * 1024];
   unsigned char buffG[1024 * 1024];
   unsigned char buffB[1024 * 1024];
   unsigned char buffBGR[1024 * 1024 * 3];
   glDrawBuffer(GL_FRONT);
   glReadBuffer(GL_FRONT);
  // break picture into 3 colors
   glReadPixels(0, 0, (int) WndSize.getX(), (int) WndSize.getY(), GL_RED, GL_UNSIGNED_BYTE, buffR);
   glReadPixels(0, 0, (int) WndSize.getX(), (int) WndSize.getY(), GL_GREEN, GL_UNSIGNED_BYTE, buffG);
   glReadPixels(0, 0, (int) WndSize.getX(), (int) WndSize.getY(), GL_BLUE, GL_UNSIGNED_BYTE, buffB);

//   glReadPixels(0, 0, (int) WndSize.getX(), (int) WndSize.getY(), GL_RGB, GL_UNSIGNED_BYTE, buffBGR);
  // reverse the colors (BGR) except it doesn't exactly work
   glAccum(GL_LOAD, 1);
   glDrawPixels( (int) WndSize.getX(), (int) WndSize.getY(), GL_BLUE, GL_UNSIGNED_BYTE, buffR);
   glAccum(GL_ACCUM, 1);
   glDrawPixels( (int) WndSize.getX(), (int) WndSize.getY(), GL_GREEN, GL_UNSIGNED_BYTE, buffG);
   glAccum(GL_ACCUM, 1);
   glDrawPixels( (int) WndSize.getX(), (int) WndSize.getY(), GL_RED, GL_UNSIGNED_BYTE, buffB);
   glReadBuffer(GL_FRONT);
   glDrawBuffer(GL_FRONT);
   glAccum(GL_RETURN, 1);
   glReadPixels(0, 0, (int) WndSize.getX(), (int) WndSize.getY(), GL_RGB, GL_UNSIGNED_BYTE, buffBGR);
  // write the BGR pixels in a stream to the targa file
   tga.write((char*) buffBGR, header.width * header.height * 3);
   cout << "Width: " << header.width << "    Height: " << header.height << endl;
/* close the file */
/* that was easy, right? */
   return 0;
}


static void Reshape( int width, int height )  // Essential Magic again!
{
   glViewport(0, 0, (GLint)width, (GLint)height);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho( -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 );
   glMatrixMode(GL_MODELVIEW);
   WndSize.assign(width, height);
   changed = 1;
 //  
cout << "reshaped" << endl;
}


static void key_up()  //  A Function accessing the UP ARROW Key
{
   base.y1+=((base.y2 - base.y1) * .1);
   base.y2+=((base.y2 - base.y1) * .1);//this moves the graph up
   changed = 1;
}


static void key_down()  // A Function accessing the DOWN ARROW Key
{
   base.y1-=((base.y2 - base.y1) * .1);
   base.y2-=((base.y2 - base.y1) * .1);//this moves the graph down 1% of the viewing rectangle
   changed = 1;
}
static void key_write()
{
  writetargafile();
}

static void key_right() // right key
{
  base.x1+=((base.x2 - base.x1) * .1);
  base.x2+=((base.x2 - base.x1) * .1);// this moves the graph to the right
   changed = 1;
}
static void key_left() // right key
{
  base.x1-=((base.x2 - base.x1) * .1);
  base.x2-=((base.x2 - base.x1) * .1);// this moves the graph to the left
   changed = 1;
}

static void key_more()
{ // increase iterations of twist and flip
  res++;
   changed = 1;
  cout << "Iterations: " << res << endl;
}
static void key_less()
{ // decrease iterations
  res--;
   changed = 1;
  cout << "Iterations: " << res << endl;
}

static void key_plus()
{
  float newwidth, newheight;
  newwidth = (base.x2 - base.x1) * .8;
  base.x1 = (base.x2 - base.x1) * .1 + base.x1;
  base.x2 = base.x1 + newwidth;
  newheight = (base.y2 - base.y1) * .8;
  base.y1 = (base.y2 - base.y1) * .1 + base.y1;
  base.y2 = base.y1 + newheight;// this zooms in on the graph at the center of the screen
   changed = 1;
}

static void key_minus()
{
  float newwidth, newheight;
  newwidth = (base.x2 - base.x1) * 1.20;
  base.x1 = base.x1 - (base.x2 - base.x1) * .1;
  base.x2 = base.x1 + newwidth;
  newheight = (base.y2 - base.y1) * 1.2;
  base.y1 = base.y1 - (base.y2 - base.y1) * .1;
  base.y2 = base.y1 + newheight;// zooms out
   changed = 1;
}



void savebuffer()
 {// save screen to buffer in memory
    glReadPixels(0, 0, (int) WndSize.getX(), (int) WndSize.getY(), GL_RGB, GL_UNSIGNED_BYTE, buffBGR);//save all colors
    glReadPixels(0, 0, (int) WndSize.getX(), (int) WndSize.getY(), GL_RED, GL_UNSIGNED_BYTE, buffR);//save individual R G B values to different
    glReadPixels(0, 0, (int) WndSize.getX(), (int) WndSize.getY(), GL_GREEN, GL_UNSIGNED_BYTE, buffG);// buffers
    glReadPixels(0, 0, (int) WndSize.getX(), (int) WndSize.getY(), GL_BLUE, GL_UNSIGNED_BYTE, buffB);
 }
void Refreshscreen(unsigned char *buff)
 {// draw screen from buffer in memory
    glDrawPixels( (int) WndSize.getX(), (int) WndSize.getY(), GL_RGB, GL_UNSIGNED_BYTE, buff);//restore screen from RGB buffer
 }


static void key_full()
 {
   Refreshscreen(buffBGR);
   cancel = 1;
 }

static void key_red()
 {// draw screen from buffer in memory
    glDrawPixels( (int) WndSize.getX(), (int) WndSize.getY(), GL_RED, GL_UNSIGNED_BYTE, buffR);//dsiplay only R componnent
   cancel = 1;
 }

static void key_green()
 {// draw screen from buffer in memomory (only G component)
    glDrawPixels( (int) WndSize.getX(), (int) WndSize.getY(), GL_GREEN, GL_UNSIGNED_BYTE, buffG);
   cancel = 1;
 }

static void key_blue()
 {// draw screen from buffer in memory (only B component)
    glDrawPixels( (int) WndSize.getX(), (int) WndSize.getY(), GL_BLUE, GL_UNSIGNED_BYTE, buffB);
   cancel = 1;
 }


static void key_save()
{
  savebuffer();
}

static void key_esc()  // A Function Accessing the ESCAPE key
{
   auxQuit();          // If pressed, the window QUITS
}

baseUpdate()  // Assigning coordinates from the object to the structure
   { 
     base.x1 = A.getX();
     base.y1 = A.getY();
     base.x2 = B.getX();
     base.y2 = B.getY();

   }     


double f(double x1, double y1, double x2, double y2) // function used to determine angle of rotation based on distance
{
  return( sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) );
}
   
double NumIt(double &x, double &y, int iter, double twistx, double twisty) // twists and  flips x and y
// around twistx and twisty iter number of times
   {
     double theta;
     for (int cnt = 0; cnt < iter; cnt++)
     {
        theta = f(x, y, twistx, twisty);
        x = (twisty - y) * sin(theta) + (twistx - x) * cos(theta) - twistx;
        y = (twistx - x) * sin(theta) - (twisty - y) * cos(theta) - twisty;
     }
	  
   }

void DrawMyStuff()  // The Drawing Routine
{ 
    double x,y, ix, iy, step = .0001; // how many points are in the line i.e. how fine resolution
      wnd.SetDestScale(-1, -1, 1, 1);
      wnd.SetSourceScale(base.x1, base.y1, base.x2,  base.y2);//this scales the window size so we can graph it inthe window
         // by get window width and height
      cout << "Please wait..." << endl;
      glColor3f(0, 0, 0);
      glBegin(GL_LINES); // draw axis in black
      glVertex2f(base.x1, 0);
      glVertex2f(base.x2, 0);
      glVertex2f(0, base.y1);
      glVertex2f(0, base.y2);
      glEnd();
      glBegin(GL_POINTS);
      for(x = -1; x <= 1; x+=step) // first line in white
      {
        glColor3f((x + 1) / 2, 0, 0);
        ix = x; iy = -1;
        NumIt(ix, iy, res, px, py);
        wnd.SetPoint(ix, iy);
        glVertex2f(wnd.GetX(), wnd.GetY());
      }
      for (x = -1; x <=1; x+=step) // second line in red
      {
        glColor3f(2 / (x + 1), (x + 1) / 2, 0);
        ix = x; iy = 1;
        NumIt(ix, iy, res, px, py);
        wnd.SetPoint(ix, iy);
        glVertex2f(wnd.GetX(), wnd.GetY());
      }
      for (y = -1; y <=1; y+=step)
      {
        glColor3f(0, (y + 1) / 2, 0); // third line in green
        ix = -1; iy = y;
        NumIt(ix, iy, res, px, py);
        wnd.SetPoint(ix, iy);
        glVertex2f(wnd.GetX(), wnd.GetY());
      }
      for (y = -1; y <=1; y+=step)
      {
        glColor3f(0, 0, (y + 1) / 2); // and fourth in blue
        ix = 1; iy = y;
        NumIt(ix, iy, res, px, py);
        wnd.SetPoint(ix, iy);
        glVertex2f(wnd.GetX(), wnd.GetY());
      }
      cout << "Done." << endl;
      glEnd();
//      savebuffer();   
} 


static void display( )  // Main display routine called from event loop
{
   
   /* clear viewport */
   glClear( GL_COLOR_BUFFER_BIT );

 
   /* draw stuff */
     DrawMyStuff();

   /* flush / swap buffers */
   glFlush();
   auxSwapBuffers();
   	
}

static void key_redraw()
{
  display();
}

static GLenum mouse(int mousex, int mousey, GLenum button)
{// takes the coordinate the mouse clicked on and converts it to the numebr scale for the Julia set
  scale invwnd;
  invwnd.SetSourceScale(0, 0, WndSize.getX(), WndSize.getY());
  invwnd.SetDestScale(base.x1, base.y1, base.x2, base.y2);
  cout << mousex << "  " << mousey << endl;
  if (button == 1)
  {
    invwnd.SetPoint(mousex, WndSize.getY() - mousey);
    px = invwnd.GetX(); // set rotation point from mouse click
    py = invwnd.GetY();
  }
//  display();
}


int main( int argc, char **argv )
{  
   float x,y; 
   // Enter points
   cout << "This program will graph a parabola within specified ranges.\n";
   cout << "You will be asked to enter two points.\n";
   cout << "Lower left corner\n";
   A.getPoint();
   cout << "Upper right corner\n";
   B.getPoint();

   baseUpdate();  // Reassign values to the Structure

   auxInitDisplayMode( AUX_RGBA );  // Initialize Display in RGB mode

   auxInitPosition( 50,50, 50, 50 );  // Draw a window starting at (50,50)
                                        // with size of 400 pixels for both
                                        // X and Y dimensions.

   if (auxInitWindow("My Window") == GL_FALSE)  // Create window and give it 
    { 						// a name.  If problems, quit. 
      auxQuit();
    }

   Init();  // Call Init routine

   auxExposeFunc(Reshape);  //  More Magic

   auxReshapeFunc(Reshape);
   tkMouseDownFunc(mouse);
   auxKeyFunc( AUX_UP, key_up );
   auxKeyFunc( AUX_DOWN, key_down );
   auxKeyFunc( AUX_RIGHT, key_right);
   auxKeyFunc( AUX_LEFT, key_left);
   auxKeyFunc( AUX_a, key_minus);
   auxKeyFunc( AUX_z, key_plus);
   auxKeyFunc( AUX_q, key_red);
   auxKeyFunc( AUX_w, key_green);
   auxKeyFunc( AUX_e, key_blue);
   auxKeyFunc( AUX_s, key_save);
   auxKeyFunc( AUX_r, key_full);
   auxKeyFunc( AUX_t, key_write);
   auxKeyFunc( AUX_o, key_less);
   auxKeyFunc( AUX_p, key_more);
   auxKeyFunc( AUX_m, key_redraw);
   auxMainLoop( display );  // Call display loop with display function

   return 0;
}
