/*
	ampex1

	mk@lemo.dk, 29-jan-2025

*/


#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#include <math.h>
#include <assert.h>
#include <GL/freeglut_ext.h>

#define MirrorY 1175.0f
#define TopReelX 233.0f
#define TopReelY (MirrorY-267.0f)
#define TopReelOuterR 215.0f
#define TopReelInnerR 94.0f
#define TopCornerX 484.0f
#define TopCornerY (MirrorY-247.0f)
#define TopCornerOuterR 11.0f
#define TopCornerInnerR 5.0f
#define TopChamperInX 396.0f
#define TopChamperInY (MirrorY-483.0f)
#define TopChamperInInnerR 9.0f
#define TopChamperInOuterR 34.5f
#define TopChamperOuter1X 389.0f
#define TopChamperOuter1Y (MirrorY-488.0f)
#define TopChamperOuter2X 725.0f
#define TopChamperOuter2Y (MirrorY-29.0f)
#define TopChamperOuter3X 807.0f
#define TopChamperOuter3Y (MirrorY-95.0f)
#define TopChamperOuter4X 472.0f
#define TopChamperOuter4Y (MirrorY-555.0f)
#define TopChamperInner1X 397.0f
#define TopChamperInner1Y (MirrorY-496.0f)
#define TopChamperInner2X 728.0f
#define TopChamperInner2Y (MirrorY-46.0f)
#define TopChamperInner3X 793.0f
#define TopChamperInner3Y (MirrorY-98.0f)
#define TopChamperInner4X 463.0f
#define TopChamperInner4Y (MirrorY-549.0f)
#define TopChamperOutX 473.0f
#define TopChamperOutY (MirrorY-536.0f)
#define TopChamperOutR 10.0f
#define Head1X 546.0f
#define Head1Y (MirrorY-579.0f)
#define Head2X 546.0f
#define Head2Y (MirrorY-543.0f)
#define Head3X 577.0f
#define Head3Y (MirrorY-523.0f)
#define Head4X 601.0f
#define Head4Y (MirrorY-523.0f)
#define Head5X 631.0f
#define Head5Y (MirrorY-543.0f)
#define Head6X 631.0f
#define Head6Y (MirrorY-579.0f)
#define CapstanX 706.0f
#define CapstanY (MirrorY-593.0f)
#define CapstanR 43.0f
#define BottomReelX 233.0f
#define BottomReelY 267.0f
#define BottomReelOuterR 215.0f
#define BottomReelInnerR 94.0f
#define BottomCornerX 484.0f
#define BottomCornerY 247.0f
#define BottomCornerOuterR 11.0f
#define BottomCornerInnerR 5.0f
#define BottomChamperInX 396.0f
#define BottomChamperInY 483.0f
#define BottomChamperInOuterR 34.5f
#define BottomChamperOuter1X 389.0f
#define BottomChamperOuter1Y 488.0f
#define BottomChamperOuter2X 725.0f
#define BottomChamperOuter2Y 29.0f
#define BottomChamperOuter3X 807.0f
#define BottomChamperOuter3Y 95.0f
#define BottomChamperOuter4X 472.0f
#define BottomChamperOuter4Y 555.0f
#define BottomChamperInner1X 397.0f
#define BottomChamperInner1Y 496.0f
#define BottomChamperInner2X 728.0f
#define BottomChamperInner2Y 46.0f
#define BottomChamperInner3X 793.0f
#define BottomChamperInner3Y 98.0f
#define BottomChamperInner4X 463.0f
#define BottomChamperInner4Y 549.0f
#define BottomChamperOutX 473.0f
#define BottomChamperOutY 536.0f
#define BottomChamperOutR 10.0f

#define XMAX 830
#define YMAX 1170

/* The number of our GLUT window */
int window; 
int window_width, window_height;
int fullscreen=0;
GLfloat window_aspect;

#define SQR(x)  ((x)*(x))

GLuint loop;             // general loop variable

int xmin, xmax, ymin, ymax;

GLfloat LightAmbient[]  = {0.5f, 0.5f, 0.5f, 1.0f}; 
GLfloat LightDiffuse[]  = {1.0f, 1.0f, 1.0f, 1.0f}; 
GLfloat LightPosition[] = {0.0f, 0.0f, 2.0f, 1.0f};

/* storage for one texture  */
GLuint texture[1];

/* Image type - contains height, width, and data */
struct Image {
    unsigned int sizeX;
    unsigned int sizeY;
    char *data;
};
typedef struct Image Image;

// quick and dirty bitmap loader...for 32 bit bitmaps with 1 plane only.  
// See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/BMP.txt for more info.
int ImageLoad(char *filename, Image *image) {
    FILE *file;
    unsigned long size;                 // size of the image in bytes.
    unsigned long i;                    // standard counter.
    unsigned short int planes;          // number of planes in image (must be 1) 
    unsigned short int bpp;             // number of bits per pixel (must be 32)

    // make sure the file is there.
    if ((file = fopen(filename, "rb"))==NULL)
    {
	printf("File Not Found : %s\n",filename);
	return 0;
    }
    
    // seek through the bmp header, up to the width/height:
    fseek(file, 18, SEEK_CUR);

    // read the width
    if ((i = fread(&image->sizeX, 4, 1, file)) != 1) {
	printf("Error reading width from %s.\n", filename);
	return 0;
    }
    printf("Width of %s: %u\n", filename, image->sizeX);
    
    // read the height 
    if ((i = fread(&image->sizeY, 4, 1, file)) != 1) {
	printf("Error reading height from %s.\n", filename);
	return 0;
    }
    printf("Height of %s: %u\n", filename, image->sizeY);
    
    // calculate the size (assuming 32 bits or 4 bytes per pixel).
    size = image->sizeX * image->sizeY * 4;

    // read the planes
    if ((fread(&planes, 2, 1, file)) != 1) {
	printf("Error reading planes from %s.\n", filename);
	return 0;
    }
    if (planes != 1) {
	printf("Planes from %s is not 1: %u\n", filename, planes);
	return 0;
    }

    // read the bpp
    if ((i = fread(&bpp, 2, 1, file)) != 1) {
	printf("Error reading bpp from %s.\n", filename);
	return 0;
    }
    if (bpp != 32) {
	printf("Bpp from %s is not 32: %u\n", filename, bpp);
	return 0;
    }
	
    // seek past the rest of the bitmap header.
    fseek(file, 24, SEEK_CUR);

    // read the data. 
    image->data = (char *) malloc(size);
    if (image->data == NULL) {
	printf("Error allocating memory for color-corrected image data");
	return 0;	
    }

    if ((i = fread(image->data, size, 1, file)) != 1) {
	printf("Error reading image data from %s.\n", filename);
	return 0;
    }
    // we're done.
    return 1;
}

static void circle(GLfloat x0, GLfloat y0, GLfloat r, GLfloat v0, GLfloat v1)
{
  GLfloat lastx,lasty,x,y,v;
  int i,n;

  n=r*2;
  lastx=x0+r*cosf(v0/180.0f*M_PI);
  lasty=y0+r*sinf(v0/180.0f*M_PI);
  for(i=1;i<=n;i++)
  {
    v=v0+(v1-v0)*((float)i)/((float) n);
    x=x0+r*cosf(v/180.0f*M_PI);
    y=y0+r*sinf(v/180.0f*M_PI);
    glVertex3f(lastx,lasty,0.0f);
    glVertex3f(x,y,0.0f);
    lastx=x;
    lasty=y;
  }
}


    
// Load Bitmaps And Convert To Textures
void LoadGLTextures() {	
    // Load Texture
    Image *image1;
    
    // allocate space for texture
    image1 = (Image *) malloc(sizeof(Image));
    if (image1 == NULL) {
	printf("Error allocating space for image");
	exit(0);
    }

    if (!ImageLoad("spole4.bmp", image1)) {
	exit(1);
    }        

    // Create Texture	
    glGenTextures(1, &texture[0]);
    glBindTexture(GL_TEXTURE_2D, texture[0]);   // 2d texture (x and y size)

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // scale linearly when image bigger than texture
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // scale linearly when image smalled than texture

    // 2d texture, level of detail 0 (normal), 3 components (red, green, blue), x size from image, y size from image, 
    // border 0 (normal), rgb color data, unsigned byte data, and finally the data itself.
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image1->sizeX, image1->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, image1->data);
};

static void intersectionLineCircle(double a1, double c1,
                                   double x1, double y1, double r1,
				   double *xi1, double *yi1,
				   double *xi2, double *yi2)
{
  double A,B,C,d,tmp;
  A=(1.0+SQR(a1));
  B=(-2.0*x1+2.0*a1*(y1+c1));
  C=SQR(x1)+SQR(y1+c1)-SQR(r1);
  d=SQR(B)-4.0*A*C;
  if(d<0)
  {
    fprintf(stderr, "no intersection\n");
    exit(-1);
  }
  *xi1 = (-B+sqrt(d))/2.0/A;
  *xi2 = (-B-sqrt(d))/2.0/A;
  if((*xi1) > (*xi2))
  {
    tmp=*xi1;
    *xi1=*xi2;
    *xi2=tmp;
  }
  *yi1 = -c1-a1*(*xi1);
  *yi2 = -c1-a1*(*xi2);
}

static void intersectionLines(double a1, double c1,
                              double a2, double c2,
			      double *x, double *y)
{
  *x = (c1-c2)/(a2-a1);
  *y = -c1-a1*(*x);
}

static void intersectionCircles(double x1, double y1, double r1,
                                double x2, double y2, double r2,
				double *xi1, double *yi1,
				double *xi2, double *yi2)
{
  double R,tmp;
  /* https://math.stackexchange.com/questions/256100/how-can-i-find-the-points-at-which-two-circles-intersect */
  R=sqrt(SQR(x1-x2)+SQR(y1-y2));
  *xi1 = (x1+x2)/2.0 + (SQR(r1)-SQR(r2))/2.0/SQR(R)*(x2-x1)
        +0.5*sqrt(2.0*(SQR(r1)+SQR(r2))/SQR(R)-SQR(SQR(r1)-SQR(r2))/SQR(SQR(R))-1.0)*(y2-y1);
  *xi2 = (x1+x2)/2.0 + (SQR(r1)-SQR(r2))/2.0/SQR(R)*(x2-x1)
        -0.5*sqrt(2.0*(SQR(r1)+SQR(r2))/SQR(R)-SQR(SQR(r1)-SQR(r2))/SQR(SQR(R))-1.0)*(y2-y1);
  *yi1 = (y1+y2)/2.0 + (SQR(r1)-SQR(r2))/2.0/SQR(R)*(y2-y1)
        +0.5*sqrt(2.0*(SQR(r1)+SQR(r2))/SQR(R)-SQR(SQR(r1)-SQR(r2))/SQR(SQR(R))-1.0)*(x1-x2);
  *yi2 = (y1+y2)/2.0 + (SQR(r1)-SQR(r2))/2.0/SQR(R)*(y2-y1)
        -0.5*sqrt(2.0*(SQR(r1)+SQR(r2))/SQR(R)-SQR(SQR(r1)-SQR(r2))/SQR(SQR(R))-1.0)*(x1-x2);
}

static void points2Line(double x1, double y1,
                        double x2, double y2,
			double *a, double *c)
{
  *a = (y1-y2)/(x2-x1);
  *c = -(*a)*x1-y1;
}

static void twoCirclesTangent(double x1, double y1, double r1,
                              double x2, double y2, double r2,
			      int transverse,
			      double *xi11, double *yi11,
			      double *xi12, double *yi12,
			      double *xi21, double *yi21,
			      double *xi22, double *yi22)
{
  /* https://math.stackexchange.com/questions/1662110/common-tangent-to-two-circles */
  double a,c,xr,yr,ca,cc,cx1,cy1,cx2,cy2,ca2,cc2,x3,y3,r3,angle;
  points2Line(x1,y1,x2,y2,&ca,&cc);
  angle=atan2(y2-y1,x2-x1)+M_PI_2;
  cx1=x1+r1*cos(angle);
  cy1=y1+r1*sin(angle);
  cx2=x2+r2*cos(angle+(transverse?M_PI:0));
  cy2=y2+r2*sin(angle+(transverse?M_PI:0));
  points2Line(cx1,cy1,cx2,cy2,&ca2,&cc2);
  intersectionLines(ca,cc,ca2,cc2,&xr,&yr);
  x3=(x2+xr)/2.0;
  y3=(y2+yr)/2.0;
  r3=sqrt(SQR(xr-x2)+SQR(yr-y2))/2.0;
  intersectionCircles(x2,y2,r2,x3,y3,r3,xi21,yi21,xi22,yi22);
  x3=(x1+xr)/2.0;
  y3=(y1+yr)/2.0;
  r3=sqrt(SQR(xr-x1)+SQR(yr-y1))/2.0;
  intersectionCircles(x1,y1,r1,x3,y3,r3,xi11,yi11,xi12,yi12);
}

/*
void Screen2Pixel(int screenx, int screeny, int *x, int *y)
{
  *x = ((GLfloat) screenx)/((GLfloat) window_width)*(xmaxa-xmina)+xmina;
  *y = ((GLfloat) screeny)/((GLfloat) window_height)*(ymaxa-ymina)+ymina;
}
*/

GLvoid InitGL(GLsizei Width, GLsizei Height)
{
  // printf("InitGL: %d %d\n", Width, Height);

  window_width=Width;
  window_height=Height;
  window_aspect = ((GLfloat) Width)/((GLfloat) Height)*((GLfloat) YMAX)/((GLfloat) XMAX);

  LoadGLTextures();				// Load The Texture(s) 
  glEnable(GL_TEXTURE_2D);			// Enable Texture Mapping
  glClearColor(1.0f, 1.0f, 1.0f, 0.0f);	// Clear The Background Color To Blue 
  glClearDepth(1.0);				// Enables Clearing Of The Depth Buffer
  glDepthFunc(GL_LESS);			// The Type Of Depth Test To Do
//glEnable(GL_DEPTH_TEST);			// Enables Depth Testing
  glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

  glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
  glMatrixMode(GL_MODELVIEW);

  glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
  glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
  glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
  glEnable(GL_LIGHT1);
}

GLvoid ReSizeGLScene(GLsizei Width, GLsizei Height)
{
  if (Height==0) Height=1;
  // printf("ReSize: %d %d\n", Width, Height);
  window_width=Width;
  window_height=Height;
  window_aspect = ((GLfloat) Width)/((GLfloat) Height)*((GLfloat) YMAX)/((GLfloat) XMAX);

  glViewport(0, 0, window_width, window_height);

}

static float zrot=0.0f;
static float dzrot=0.1f;

/* The main drawing function. */
GLvoid DrawGLScene(GLvoid)
{
  char txt[100];
  float x,y;
  int width, height;
  int widthr, heightr;
  GLfloat window_xmin, window_xmax, window_ymin, window_ymax;
  GLfloat xcenter, ycenter, aspect, xsize, ysize;
  double a1,c1,xi1,yi1,xi2,yi2,angle1,angle2;

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
//glViewport(0, 0, window_width, window_height);
  glOrtho(0.0f,((float)XMAX)*window_aspect,0.0f,((float)YMAX),-1.0f,1.0f);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear The Screen And The Depth Buffer
  glLoadIdentity();				// Reset The View
  glDisable(GL_BLEND);

  glBegin(GL_LINES);
  glColor4f(0.0f,0.0f,0.0f,1.0f);
  glVertex3f(BottomReelX-BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0f);
  glVertex3f(BottomReelX-BottomReelOuterR,BottomReelY+BottomReelOuterR,0.0f);

  glVertex3f(BottomReelX-BottomReelOuterR,BottomReelY+BottomReelOuterR,0.0f);
  glVertex3f(BottomReelX+BottomReelOuterR,BottomReelY+BottomReelOuterR,0.0f);

  glVertex3f(BottomReelX+BottomReelOuterR,BottomReelY+BottomReelOuterR,0.0f);
  glVertex3f(BottomReelX+BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0f);

  glVertex3f(BottomReelX+BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0f);
  glVertex3f(BottomReelX-BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0f);

  circle(TopReelX, TopReelY, TopReelOuterR, 0.0f, 360.0f);
  circle(TopReelX, TopReelY, TopReelInnerR, 0.0f, 360.0f);

  circle(TopCornerX, TopCornerY, TopCornerOuterR, 0.0f, 360.0f);
  circle(TopCornerX, TopCornerY, TopCornerInnerR, 0.0f, 360.0f);

  circle(TopChamperInX, TopChamperInY, TopChamperInInnerR, 0.0f, 360.0f);

  points2Line(TopChamperOuter1X, TopChamperOuter1Y, TopChamperOuter2X, TopChamperOuter2Y,
              &a1, &c1);
  intersectionLineCircle(a1,c1,TopChamperInX,TopChamperInY,TopChamperInInnerR,
              &xi1,&yi1,&xi2,&yi2);

  glVertex3f(xi2,yi2,0.0f);
  glVertex3f(TopChamperOuter2X,TopChamperOuter2Y,0.0f);

  intersectionLineCircle(a1,c1,TopChamperInX,TopChamperInY,TopChamperInOuterR,
              &xi1,&yi1,&xi2,&yi2);

  angle1=-atan2(yi2-TopChamperInY,xi2-TopChamperInX)*180.0/M_PI;
  points2Line(TopChamperOuter4X, TopChamperOuter4Y, TopChamperOuter1X, TopChamperOuter1Y,
              &a1,&c1);
  intersectionLineCircle(a1,c1,TopChamperInX,TopChamperInY,TopChamperInOuterR,
              &xi1,&yi1,&xi2,&yi2);
  angle2=360.0-atan2(yi2-TopChamperInY,xi2-TopChamperInX)*180.0/M_PI;

  printf("angle1: %lg angle2: %lg\n",angle1,angle2);
  circle(TopChamperInX, TopChamperInY, TopChamperInOuterR, angle1, angle2);


  
  // kilroy
  glEnd();

  glEnable(GL_BLEND);
  glTranslatef(BottomReelX-BottomReelOuterR,BottomReelY-BottomReelOuterR,0.0f);
  glScalef(BottomReelOuterR*2.0f,BottomReelOuterR*2.0f,1.0f);
  glTranslatef(0.5f,0.5f,0.0f);
  glRotatef(zrot,0.0f,0.0f,1.0f);
  glTranslatef(-0.5f,-0.5f,0.0f);
  zrot+=dzrot;
  
  glBindTexture(GL_TEXTURE_2D, texture[0]);   // choose the texture to use.

  glPolygonMode(GL_BACK, GL_FILL);
  glPolygonMode(GL_FRONT, GL_LINE);

  glBegin(GL_QUADS);
    glColor4f(1.0f,1.0f,1.0f,1.0f);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(0.0f, 1.0f, 0.0f);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 0.0f);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(1.0f, 0.0f, 0.0f);

  glEnd();



  // since this is double buffered, swap the buffers to display what just got drawn.
  glutSwapBuffers();

}

void keyPressed(unsigned char key, int x, int y) 
{

  switch (key)
  {    
    case '\e':
    case 'q':
    case 'Q':
      exit(1);                   	
      break;

    case 'f':
    case 'F':
      if(fullscreen)
      {
	glutReshapeWindow(640, 480);
	fullscreen=0;
      }
      else
      {
	glutFullScreen();
	fullscreen=1;
      }
	break;
    default:
      // printf ("Key %d pressed. No action there yet.\n", key);
      break;
  }	
  DrawGLScene();
}

void specialKeyPressed(int key, int x, int y) 
{
  int width, height;
  width=xmax-xmin+1;
  height=ymax-ymin+1;

  switch (key)
  {    
    case GLUT_KEY_PAGE_UP:
      break;
    case GLUT_KEY_PAGE_DOWN:
      break;
    case GLUT_KEY_UP:
      break;
    case GLUT_KEY_DOWN:
      break;
    case GLUT_KEY_LEFT:
      break;
    case GLUT_KEY_RIGHT:
      break;
    case GLUT_KEY_HOME:
      break;

    default:
	// printf ("Special key %d pressed. No action there yet.\n", key);
	break;
  }	
  DrawGLScene();
}

void mouseFunc(int button, int state, int x, int y)
{
  int width, height;
  int picturex, picturey;
  double distance;
  /*
  	button:

        0: Left
        1: Middle
        2: Right
        3: Wheel up
        4: Wheel down
  */
}

static void idle()
{
  glutSetWindow(window);
  glutPostRedisplay();
}


int main(int argc, char **argv)
{
  int i;
  float lineWidth[2];
  
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
  glutInitWindowSize(640, 480);
  glutInitWindowPosition(0, 0);
  window = glutCreateWindow("ampex1");

  glutDisplayFunc(&DrawGLScene);
  /* glutFullScreen(); */

  glutIdleFunc(&idle);
  glutReshapeFunc(&ReSizeGLScene);
  glutKeyboardFunc(&keyPressed);
  glutSpecialFunc(&specialKeyPressed);
  glutMouseFunc(&mouseFunc);
  InitGL(1024, 1024);
  glGetFloatv(GL_LINE_WIDTH_RANGE, lineWidth);
  printf("linewidth: %g %g\n", lineWidth[0], lineWidth[1]);
  glutMainLoop();

  return 1;
}

