/*
 * Rubik's clock.
 *
 * This file is public domain, I just hate copy[left/right]s.
 *-------------
 * the screen handling stuff (showing, drawing) file
 */

#define _LOCAL_SCREEN_H_
#include "screen.h"

#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <stdio.h>
#include <math.h>
#include <string.h>

#include "version.h"
#include "event.h"

char *colname[];

/* return -1 on error, 0 otherwise */
int init_screen(SCREEN *s)
{
  int i;
  XEvent e;
  XColor col, dum;
  XGCValues val;

  memset(s, 0, sizeof(SCREEN));

  /* 1 - open the screen */
  if (!(s->d=XOpenDisplay(0))) {
    fprintf(stderr, "init_screen error : unable to XOpenDisplay. Are you under X ?\n");
    return -1;
  }

  /* 2 - create a window and map it */
  s->w=XCreateSimpleWindow(s->d, DefaultRootWindow(s->d), 0, 0, SIZEX, SIZEY, 0,
			   BlackPixel(s->d, DefaultScreen(s->d)), BlackPixel(s->d, DefaultScreen(s->d)));
  XSelectInput(s->d, s->w, KeyPressMask | ButtonPressMask | ExposureMask | PointerMotionMask);
  XStoreName(s->d, s->w, NAME " " VERSION);
  XMapWindow(s->d, s->w);
  /* let's wait for it to be mapped */
  do
    XNextEvent(s->d, &e);
  while (e.type != Expose);
  s->sizex=SIZEX;
  s->sizey=SIZEY;

  /* 3 - alloc colors */
  for (i=0; i<9; i++) {
    if (!XAllocNamedColor(s->d, DefaultColormap(s->d, DefaultScreen(s->d)), colname[i], &col, &dum)) {
      fprintf(stderr, "init_screen error : impossible to alloc a color\n");
      return -1;
    }
    s->colors[i]=col.pixel;
  }

  /* 4 - let's use a private GC */
  val.foreground=WhitePixel(s->d, DefaultScreen(s->d));
  val.background=BlackPixel(s->d, DefaultScreen(s->d));
  val.line_width=1;
  val.fill_style=FillSolid;
  val.arc_mode=ArcPieSlice; //what is the difference ? maybe I should go read xfree code to check...
  s->gc=XCreateGC(s->d, s->w, GCForeground | GCBackground | GCLineWidth | GCFillStyle | GCArcMode, &val);

  /* 5 - load the cursors */
  s->cursor_on=XCreateFontCursor(s->d, XC_plus);
  s->cursor_off=XCreateFontCursor(s->d, XC_top_left_arrow);

  return 0;
}

void close_screen(SCREEN *s)
{
  XCloseDisplay(s->d);
}

static void set_color(SCREEN *s, int col)
{
  XGCValues val;

 val.foreground=s->colors[col];
  XChangeGC(s->d, s->gc, GCForeground, &val);
}

static void set_line_width(SCREEN *s, int width)
{
  XGCValues val;

  val.line_width=width;
  XChangeGC(s->d, s->gc, GCLineWidth, &val);
}

static void draw_fleche(SCREEN *s, float angle, int p1x, int p1y, int p2x, int p2y, int p3x, int p3y)
{
  XPoint p[3];
  /* surely one can make better than this poor code, but yah know... */
  float np1x, np1y, np2x, np2y, np3x, np3y;
  float cosa=cos(-M_PI*2*angle/360);
  float sina=sin(-M_PI*2*angle/360);

  /* let's turn it all */
  np1x=cosa*p1x-sina*p1y;
  np1y=sina*p1x+cosa*p1y;

  np2x=cosa*p2x-sina*p2y;
  np2y=sina*p2x+cosa*p2y;

  np3x=cosa*p3x-sina*p3y;
  np3y=sina*p3x+cosa*p3y;

  /* let's center it all */
  p[1].x=np1x+(float)s->sizex/2;
  p[1].y=np1y+(float)s->sizex/2;
  p[0].x=np2x+(float)s->sizex/2;
  p[0].y=np2y+(float)s->sizex/2;
  p[2].x=np3x+(float)s->sizex/2;
  p[2].y=np3y+(float)s->sizex/2;

  /* let's draw */
  XDrawLines(s->d, s->w, s->gc, p, 3, CoordModeOrigin);
}

static void draw_fleche1(SCREEN *s, int angle)
{
  float p1x=(float)s->sizex*1.05/2, p1y=0, p2x=p1x+5, p2y=-10, p3x=p1x-5, p3y=-10;

  draw_fleche(s, angle, p1x, p1y, p2x, p2y, p3x, p3y);
}

static void draw_fleche2(SCREEN *s, float angle)
{
  float p1x=(float)s->sizex*1.05/2, p1y=0, p2x=p1x+5, p2y=10, p3x=p1x-5, p3y=10;

  draw_fleche(s, angle, p1x, p1y, p2x, p2y, p3x, p3y);
}

/* this function draws the little red point to tell where we are in the corners key-cuts stuff */
static void draw_select(SCREEN *s, int which, int col)
{
  float px, py;

  if (which>1)
    which ^= 1;

  px=cos(5*M_PI/4+(float)which*M_PI/2)*(float)s->sizex/2+(float)s->sizex/2;
  py=sin(5*M_PI/4+(float)which*M_PI/2)*(float)s->sizex/2+(float)s->sizex/2;

  set_color(s, col);
  XFillArc(s->d, s->w, s->gc, px-5, py-5, 10, 10, 0, 360*64);
}

void draw_back(SCREEN *s, int face)
{
  int angle[]={
    30, 50, 120, 140, 210, 230, 300, 320
  };
  int i;

  set_color(s, face ? BLUE2 : BLUE);
  XFillArc(s->d, s->w, s->gc, (float)s->sizex/2-(float)s->sizex*.48, (float)s->sizex/2-(float)s->sizex*.48,
	   (float)s->sizex*0.96, (float)s->sizex*0.96, 0, 360*64);

  set_line_width(s, 5);
  set_color(s, YELLOW);
  for (i=0; i<8; i++) {
    #define SIZE 1.05
    XDrawArc(s->d, s->w, s->gc, (float)s->sizex/2-(float)s->sizex*SIZE/2, (float)s->sizex/2-(float)s->sizex*SIZE/2,
	     (float)s->sizex*SIZE, (float)s->sizex*SIZE, angle[i]*64, 10*64);
    if (!(i&1))
      draw_fleche1(s, angle[i]);
    else
      draw_fleche2(s, angle[i]+10);
  }

  XDrawString(s->d, s->w, s->gc, s->sizex/2-6*4-20, s->sizey-20, "TURN", 4);
  XDrawString(s->d, s->w, s->gc, s->sizex/2+20, s->sizey-20, "QUIT", 4);
  set_line_width(s, 3);

  draw_select(s, s->current_corner, RED);
}

void draw_clock(SCREEN *s, int x, int y, int face, int hour)
{
  int i;

  float px, py;
  float px2, py2;

  /* randomize_clock sends us a 0 value for SCREEN * */
  if (!s)
    return;

  px=(float)s->sizex/2+(float)s->sizex/4*(x-1);
  py=(float)s->sizex/2+(float)s->sizex/4*(y-1);

  set_color(s, face ? WHITE : CYAN);
  XFillArc(s->d, s->w, s->gc, px-(float)s->sizex/10, py-(float)s->sizex/10, s->sizex/5, s->sizex/5, 0, 360*64);

  hour-=3;

  px2=cos((float)hour*M_PI/6)*(float)s->sizex/12+px;
  py2=sin((float)hour*M_PI/6)*(float)s->sizex/12+py;

  set_color(s, RED);
  XDrawLine(s->d, s->w, s->gc, px, py, px2, py2);

  set_color(s, YELLOW);
  for (i=0; i<12; i++) {
    px2=cos((float)i*M_PI/6)*(float)s->sizex/9+px;
    py2=sin((float)i*M_PI/6)*(float)s->sizex/9+py;
    if (s->sizex/400>=2)
      XFillArc(s->d, s->w, s->gc, px2-(float)s->sizex/400, py2-(float)s->sizex/400, s->sizex/200, s->sizex/200, 0, 360*64);
    else
      XFillArc(s->d, s->w, s->gc, px2-1, py2-1, 2, 2, 0, 360*64);
  }
}

void draw_button(SCREEN *s, int nb, int val, int face)
{
  float px, py;

  /* randomize_clock sends us a 0 value for SCREEN * */
  if (!s)
    return;

  px=(float)s->sizex/3*((nb&1)+1);
  py=(float)s->sizex/3*((nb>>1)+1);
  px-=(float)s->sizex/2;
  py-=(float)s->sizex/2;
  px*=0.8;
  py*=0.8;
  px+=(float)s->sizex/2-(float)s->sizex/24;
  py+=(float)s->sizex/2-(float)s->sizex/24;

  if (!val) {
    /* down */
    set_color(s, face ? BLUE2 : BLUE);
    XFillArc(s->d, s->w, s->gc, px, py, s->sizex/12, s->sizex/12, 0, 360*64);
    XFillArc(s->d, s->w, s->gc, px-2, py-2, s->sizex/12-2, s->sizex/12-2, 0, 360*64);
    set_color(s, YELLOW);
    XFillArc(s->d, s->w, s->gc, px, py, s->sizex/12, s->sizex/12, 0, 360*64);
  } else {
    /* up */
    set_color(s, face ? BLUE2 : BLUE);
    XFillArc(s->d, s->w, s->gc, px, py, s->sizex/12, s->sizex/12, 0, 360*64);
    set_color(s, BLACK);
    XFillArc(s->d, s->w, s->gc, px, py, s->sizex/12, s->sizex/12, 0, 360*64);
    set_color(s, YELLOW);
    XFillArc(s->d, s->w, s->gc, px-2, py-2, s->sizex/12-2, s->sizex/12-2, 0, 360*64);
  }
}

void draw_mouse_on(SCREEN *s)
{
  XDefineCursor(s->d, s->w, s->cursor_on);
}

void draw_mouse_off(SCREEN *s)
{
  XDefineCursor(s->d, s->w, s->cursor_off);
}

int mouse_on_button(SCREEN *s, float x, float y)
{
  int i;

  for (i=0; i<4; i++) {
    /* (px, py) is the center of the button */
    float px=(float)s->sizex/3*((i&1)+1),
	  py=(float)s->sizex/3*((i>>1)+1);
    float d;

    px-=(float)s->sizex/2;
    py-=(float)s->sizex/2;
    px*=0.8;
    py*=0.8;
    px+=(float)s->sizex/2;
    py+=(float)s->sizex/2;

    /* let's calculate the distance between (x,y) and (px, py) (square distance) */
    d=(px-x)*(px-x)+(py-y)*(py-y);
    /* if distance < s->sizex/20 (20 for some margin), ok */
    if (d < ((float)s->sizex/20)*((float)s->sizex/20))
      return i;
  }

  return -1;
}

int mouse_on_arrow(SCREEN *s, float x, float y)
{
  int angle[]={
    30, 50, 120, 140, 210, 230, 300, 320
  };
  int i;
  float ro, theta;

  /* first of all, transform the (x,y) in (ro, theta) */
  /* we first must centrate (x,y) on (sizex/2, sizex/2) */
  x-=s->sizex/2;
  y-=s->sizex/2;
  /* then, let's do it (go get some math lessons to understand )*/
  ro=sqrt(x*x+y*y);
  theta=atan2(y, x)*180/M_PI;
  if (theta<0)
    theta+=360;

  /* if ro not in the right range, well fuck off */
  if (ro < (float)s->sizex/2*1.05-10)
    return -1;

  if (ro > (float)s->sizex/2*1.05+10)
    return -1;

  /* the right angle */
  for (i=0; i<8; i++)
    if (theta>angle[i]-1 && theta<angle[i]+11)
      return i;

  return -1;
}

int mouse_on_turn(SCREEN *s, int x, int y)
{
  if (x<s->sizex/2-6*4-20-5)
    return -1;

  if (x>s->sizex/2-20+5)
    return -1;

  if (y<s->sizey-20-5-8)
    return -1;

  if (y>s->sizey-20+8+5-8)
    return -1;

  return 0;
}

int mouse_on_quit(SCREEN *s, int x, int y)
{
  if (x<s->sizex/2+20-5)
    return -1;

  if (x>s->sizex/2+20+6*4+5)
    return -1;

  if (y<s->sizey-20-5-8)
    return -1;

  if (y>s->sizey-20+8+5-8)
    return -1;

  return 0;
}

void select_corner(SCREEN *s, int which)
{
  if (which==s->current_corner)
    return;

  draw_select(s, s->current_corner, BLACK);
  draw_select(s, which, RED);
  s->current_corner=which;
}

void get_mouse_position(SCREEN *s, EVENT *e, int *x, int *y)
{
  *x=0;
  *y=0;

  switch(e->type) {
    case BUTTON : {
      float px, py;
      px=(float)s->sizex/3*((e->which_button&1)+1);
      py=(float)s->sizex/3*((e->which_button>>1)+1);
      px-=(float)s->sizex/2;
      py-=(float)s->sizex/2;
      px*=0.8;
      py*=0.8;
      px+=(float)s->sizex/2;
      py+=(float)s->sizex/2;
      *x=px;
      *y=py;
      break;
    }
    case CORNER : {
      int angle[]={
	35, 55, 125, 145, 215, 235, 305, 325
      };

      *x=cos(M_PI*2*angle[e->which_corner]/360)*(float)s->sizex*1.05/2+(float)s->sizex/2;
      *y=sin(M_PI*2*angle[e->which_corner]/360)*(float)s->sizex*1.05/2+(float)s->sizex/2;
      break;
    }
    case TURN :
      *x=(float)s->sizex/2-6*4-20+6*2;
      *y=(float)s->sizey-20-10;
      break;
  }
}

void set_mouse_position(SCREEN *s, int x, int y)
{
  XWarpPointer(s->d, None, s->w, 0, 0, 0, 0, x, y);
  //XFlush(s->d);
}
