/*
 * queens - solves n queens problem, displays
 * i make no claims that this is an optimal solution to the problem,
 * good enough for xss
 * hacked from glchess
 *
 * version 1.0 - May 10, 2002
 *
 * Copyright (C) 2002 Blair Tennessy (tennessy@cs.ubc.ca)
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 */

#ifdef STANDALONE
#define DEFAULTS       "*delay:       20000 \n" \
                       "*showFPS:     False \n" \
		       "*wireframe:   False \n"	\

# define release_queens 0
# include "xlockmore.h"
#else
# include "xlock.h"
#endif

#ifdef USE_GL

#include "gltrackball.h"
#include "chessmodels.h"

static XrmOptionDescRec opts[] = {
  {"+rotate", ".queens.rotate", XrmoptionNoArg, "false" },
  {"-rotate", ".queens.rotate", XrmoptionNoArg, "true" },
  {"+flat", ".queens.flat", XrmoptionNoArg, "false" },
  {"-flat", ".queens.flat", XrmoptionNoArg, "true" },
};

static int rotate, wire, clearbits, flat;

static argtype vars[] = {
  {&rotate, "rotate", "Rotate", "True",  t_Bool},
  {&flat,   "flat",   "Flat",   "False", t_Bool},
};

ENTRYPOINT ModeSpecOpt queens_opts = {countof(opts), opts, countof(vars), vars, NULL};

#ifdef USE_MODULES
ModStruct   queens_description =
{"queens", "init_queens", "draw_queens", NULL,
 "draw_queens", "init_queens", NULL, &queens_opts,
 1000, 1, 2, 1, 4, 1.0, "",
 "Queens", 0, NULL};

#endif

#define NONE 0
#define MINBOARD 5
#define MAXBOARD 10
#define COLORSETS 5

typedef struct {
  GLXContext *glx_context;
  Window window;
  trackball_state *trackball;
  Bool button_down_p;
  GLfloat position[4];
  int queen_list;

  int board[MAXBOARD][MAXBOARD];
  int steps, colorset, BOARDSIZE;
  double theta;
  int queen_polys;

} Queenscreen;

static Queenscreen *qss = NULL;

/* definition of white/black colors */
static const GLfloat colors[COLORSETS][2][3] = 
  { 
    {{0.43, 0.54, 0.76}, {0.8, 0.8, 0.8}},
    {{0.5, 0.7, 0.9}, {0.2, 0.3, 0.6}},
    {{0.53725490196, 0.360784313725, 0.521568627451}, {0.6, 0.6, 0.6}},
    {{0.15, 0.77, 0.54}, {0.5, 0.5, 0.5}},
    {{0.9, 0.45, 0.0}, {0.5, 0.5, 0.5}},
  };

ENTRYPOINT Bool
queens_handle_event (ModeInfo *mi, XEvent *event)
{
  Queenscreen *qs = &qss[MI_SCREEN(mi)];

  if (gltrackball_event_handler (event, qs->trackball,
                                 MI_WIDTH (mi), MI_HEIGHT (mi),
                                 &qs->button_down_p))
    return True;
  else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
    {
      qs->steps = 1024 - 1;
      return True;
    }

  return False;
}



/* returns true if placing a queen on column c causes a conflict */
static int conflictsCols(Queenscreen *qs, int c) 
{
  int i;

  for(i = 0; i < qs->BOARDSIZE; ++i)
    if(qs->board[i][c])
      return 1;

  return 0;
}

/* returns true if placing a queen on (r,c) causes a diagonal conflict */
static int conflictsDiag(Queenscreen *qs, int r, int c) 
{
  int i;

  /* positive slope */
  int n = r < c ? r : c;
  for(i = 0; i < qs->BOARDSIZE-abs(r-c); ++i)
    if(qs->board[r-n+i][c-n+i])
      return 1;

  /* negative slope */
  n = r < qs->BOARDSIZE - (c+1) ? r : qs->BOARDSIZE - (c+1);
  for(i = 0; i < qs->BOARDSIZE-abs(qs->BOARDSIZE - (1+r+c)); ++i)
    if(qs->board[r-n+i][c+n-i])
      return 1;
  
  return 0;
}

/* returns true if placing a queen at (r,c) causes a conflict */
static int conflicts(Queenscreen *qs, int r, int c) 
{
  return !conflictsCols(qs, c) ? conflictsDiag(qs, r, c) : 1;
}

/* clear board */
static void blank(Queenscreen *qs) 
{
  int i, j;

  for(i = 0; i < MAXBOARD; ++i)
    for(j = 0; j < MAXBOARD; ++j)
      qs->board[i][j] = NONE;
}

/* recursively determine solution */
static int findSolution(Queenscreen *qs, int row, int col) 
{
  if(row == qs->BOARDSIZE)
    return 1;
  
  while(col < qs->BOARDSIZE) {
    if(!conflicts(qs, row, col)) {
      qs->board[row][col] = 1;

      if(findSolution(qs, row+1, 0))
        return 1;

      qs->board[row][col] = 0;
    }

    ++col;
  }

  return 0;
}

/** driver for finding solution */
static void go(Queenscreen *qs) { while(!findSolution(qs, 0, random()%qs->BOARDSIZE)); }

/* lighting variables */
static const GLfloat front_shininess[] = {60.0};
static const GLfloat front_specular[] = {0.4, 0.4, 0.4, 1.0};
static const GLfloat ambient[] = {0.3, 0.3, 0.3, 1.0};
static const GLfloat diffuse[] = {0.8, 0.8, 0.8, 1.0};

/* configure lighting */
static void setup_lights(Queenscreen *qs) 
{

  /* setup twoside lighting */
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, qs->position);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);

  /* setup material properties */
  glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, front_shininess);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, front_specular);

  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

#define    checkImageWidth 8
#define    checkImageHeight 8
/*static GLubyte checkImage[checkImageWidth][checkImageHeight][3];*/

/* return alpha value for fading */
static GLfloat findAlpha(Queenscreen *qs) 
{
  return qs->steps < 128 ? qs->steps/128.0 : qs->steps < 1024-128 ?1.0:(1024-qs->steps)/128.0;
}

/* draw pieces */
static int drawPieces(Queenscreen *qs) 
{
  int i, j;
  int polys = 0;

  for(i = 0; i < qs->BOARDSIZE; ++i) {
    for(j = 0; j < qs->BOARDSIZE; ++j) {
      if(qs->board[i][j]) {
    	glColor3fv(colors[qs->colorset][i%2]);
	glCallList(qs->queen_list);
        polys += qs->queen_polys;
      }
      
      glTranslatef(1.0, 0.0, 0.0);
    }
    
    glTranslatef(-1.0*qs->BOARDSIZE, 0.0, 1.0);
  }
  return polys;
}

/** reflectionboard */
static int draw_reflections(Queenscreen *qs) 
{
  int i, j;
  int polys = 0;

  glEnable(GL_STENCIL_TEST);
  glStencilFunc(GL_ALWAYS, 1, 1);
  glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
  glColorMask(0,0,0,0);
  glDisable(GL_CULL_FACE);

  glDisable(GL_DEPTH_TEST);
  glBegin(GL_QUADS);

  /* only draw white squares */
  for(i = 0; i < qs->BOARDSIZE; ++i) {
    for(j = (qs->BOARDSIZE+i) % 2; j < qs->BOARDSIZE; j += 2) {
      glVertex3f(i, 0.0, j + 1.0);
      glVertex3f(i + 1.0, 0.0, j + 1.0);
      glVertex3f(i + 1.0, 0.0, j);
      glVertex3f(i, 0.0, j);
      polys++;
    }
  }
  glEnd();
  glEnable(GL_DEPTH_TEST);

  glColorMask(1, 1, 1, 1);
  glStencilFunc(GL_EQUAL, 1, 1);
  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
  
  glPushMatrix(); 
  glScalef(1.0, -1.0, 1.0);
  glTranslatef(0.5, 0.001, 0.5);
  glLightfv(GL_LIGHT0, GL_POSITION, qs->position);
  polys += drawPieces(qs);
  glPopMatrix();
  glDisable(GL_STENCIL_TEST);

  /* replace lights */
  glLightfv(GL_LIGHT0, GL_POSITION, qs->position);

  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  glColorMask(1,1,1,1);
  return polys;
}

/* draw board */
static int drawBoard(Queenscreen *qs) 
{
  int i, j;
  int polys = 0;

  glBegin(GL_QUADS);

  for(i = 0; i < qs->BOARDSIZE; ++i)
    for(j = 0; j < qs->BOARDSIZE; ++j) {
      int par = (i-j+qs->BOARDSIZE)%2;
      glColor4f(colors[qs->colorset][par][0],
		colors[qs->colorset][par][1],
		colors[qs->colorset][par][2],
		0.70);
      glNormal3f(0.0, 1.0, 0.0);
      glVertex3f(i, 0.0, j + 1.0);
      glVertex3f(i + 1.0, 0.0, j + 1.0);
      glVertex3f(i + 1.0, 0.0, j);
      glVertex3f(i, 0.0, j);
      polys++;
    }

  glEnd();

  {
    GLfloat off = 0.01;
    GLfloat w = qs->BOARDSIZE;
    GLfloat h = 0.1;

    /* Give the board a slight lip. */
    /* #### oops, normals are wrong here, but you can't tell */

    glColor3f(0.3, 0.3, 0.3);
    glBegin (GL_QUADS);
    glVertex3f (0,  0, 0);
    glVertex3f (0, -h, 0);
    glVertex3f (0, -h, w);
    glVertex3f (0,  0, w);

    glVertex3f (0,  0, w);
    glVertex3f (0, -h, w);
    glVertex3f (w, -h, w);
    glVertex3f (w,  0, w);

    glVertex3f (w,  0, w);
    glVertex3f (w, -h, w);
    glVertex3f (w, -h, 0);
    glVertex3f (w,  0, 0);

    glVertex3f (w,  0, 0);
    glVertex3f (w, -h, 0);
    glVertex3f (0, -h, 0);
    glVertex3f (0,  0, 0);

    glVertex3f (0, -h, 0);
    glVertex3f (w, -h, 0);
    glVertex3f (w, -h, w);
    glVertex3f (0, -h, w);
    glEnd();
    polys += 4;

    /* Fill in the underside of the board with an invisible black box
       to hide the reflections that are not on tiles.  Probably there's
       a way to do this with stencils instead.
     */
    w -= off*2;
    h = 5;

    glPushMatrix();
    glTranslatef (off, 0, off);
    glDisable(GL_LIGHTING);
    glColor3f(0,0,0);
    glBegin (GL_QUADS);
    glVertex3f (0,  0, 0);
    glVertex3f (0, -h, 0);
    glVertex3f (0, -h, w);
    glVertex3f (0,  0, w);

    glVertex3f (0,  0, w);
    glVertex3f (0, -h, w);
    glVertex3f (w, -h, w);
    glVertex3f (w,  0, w);

    glVertex3f (w,  0, w);
    glVertex3f (w, -h, w);
    glVertex3f (w, -h, 0);
    glVertex3f (w,  0, 0);

    glVertex3f (w,  0, 0);
    glVertex3f (w, -h, 0);
    glVertex3f (0, -h, 0);
    glVertex3f (0,  0, 0);

    glVertex3f (0, -h, 0);
    glVertex3f (w, -h, 0);
    glVertex3f (w, -h, w);
    glVertex3f (0, -h, w);
    glEnd();
    polys += 4;
    glPopMatrix();
    if (!wire)
      glEnable(GL_LIGHTING);
  }

  return polys;
}

static int display(ModeInfo *mi, Queenscreen *qs) 
{
  int max = 1024;
  int polys = 0;
  glClear(clearbits);
  
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glRotatef(current_device_rotation(), 0, 0, 1);

  /* setup light attenuation */
  /* #### apparently this does nothing */
  glEnable(GL_COLOR_MATERIAL);
  glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.8/(0.01+findAlpha(qs)));
  glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.06);

  /** setup perspective */
  glTranslatef(0.0, 0.0, -1.5*qs->BOARDSIZE);
  glRotatef(30.0, 1.0, 0.0, 0.0);
  gltrackball_rotate (qs->trackball);
  glRotatef(qs->theta*100, 0.0, 1.0, 0.0);
  glTranslatef(-0.5*qs->BOARDSIZE, 0.0, -0.5*qs->BOARDSIZE);

  /* find light positions */
  qs->position[0] = qs->BOARDSIZE/2.0 + qs->BOARDSIZE/1.4*-sin(qs->theta*100*M_PI/180.0);
  qs->position[2] = qs->BOARDSIZE/2.0 + qs->BOARDSIZE/1.4*cos(qs->theta*100*M_PI/180.0);
  qs->position[1] = 6.0;

  if(!wire) {
    glEnable(GL_LIGHTING);
    glLightfv(GL_LIGHT0, GL_POSITION, qs->position);
    glEnable(GL_LIGHT0);
  }

  /* Since the lighting attenuation trick up there doesn't seem to be working,
     let's drop the old board down and drop the new board in. */
  if (qs->steps < (max/8.0)) {
    GLfloat y = qs->steps / (max/8.0);
    y = sin (M_PI/2 * y);
    glTranslatef (0, 10 - (y * 10), 0);
  } else if (qs->steps > max-(max/8.0)) {
    GLfloat y = (qs->steps - (max-(max/8.0))) / (GLfloat) (max/8.0);
    y = 1 - sin (M_PI/2 * (1-y));
    glTranslatef (0, -y * 15, 0);
  }

  /* draw reflections */
  if(!wire) {
    polys += draw_reflections(qs);
    glEnable(GL_BLEND);
  }
  polys += drawBoard(qs);
  if(!wire)
    glDisable(GL_BLEND);

  glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.1);

  glTranslatef(0.5, 0.0, 0.5);
  polys += drawPieces(qs);

  /* rotate camera */
  if(!qs->button_down_p)
    qs->theta += .002;

  /* zero out board, find new solution of size MINBOARD <= i <= MAXBOARD */
  if(++qs->steps == max) {
    qs->steps = 0;
    blank(qs);
    qs->BOARDSIZE = MINBOARD + (random() % (MAXBOARD - MINBOARD + 1));
    qs->colorset = (qs->colorset+1)%COLORSETS;
    go(qs);
  }
  return polys;
}

#define EPSILON 0.001

#if 0
/** draws cylindermodel */
static int draw_model(int chunks, const GLfloat model[][3], int r) 
{
  int i = 0;
  int polys = 0;
  glPushMatrix();
  glRotatef(-90.0, 1.0, 0.0, 0.0);
  
  for(i = 0; i < chunks; ++i) {
    if(model[i][0] > EPSILON || model[i][1] > EPSILON) {
      polys += tube (0, 0, 0,
                     0, 0, model[i][1],
                     model[i][0], 0,
                     r, False, False, False);
/*      gluCylinder(quadric, model[i][0], model[i][1], model[i][2], r, 1);
      polys += r;*/
    }
    glTranslatef(0.0, 0.0, model[i][2]);
  }
  
  glPopMatrix();
  return polys;
}
#endif

ENTRYPOINT void reshape_queens(ModeInfo *mi, int width, int height) 
{
  GLfloat h = (GLfloat) height / (GLfloat) width;
  int y = 0;

  if (width > height * 5) {   /* tiny window: show middle */
    height = width;
    y = -height/2;
    h = height / (GLfloat) width;
  }
  glViewport(0,y, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45, 1/h, 2.0, 30.0);
  glMatrixMode(GL_MODELVIEW);
}

ENTRYPOINT void init_queens(ModeInfo *mi) 
{
  int screen = MI_SCREEN(mi);
  Queenscreen *qs;
  int poly_counts[PIECES];
  wire = MI_IS_WIREFRAME(mi);

# ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
  wire = 0;
# endif

  MI_INIT (mi, qss);
  
  qs = &qss[screen];
  qs->window = MI_WINDOW(mi);
  
  if((qs->glx_context = init_GL(mi)))
    reshape_queens(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
  else
    MI_CLEARWINDOW(mi);

  qs->trackball = gltrackball_init (False);

  qs->BOARDSIZE = 8; /* 8 cuz its classic */

  chessmodels_gen_lists(-1, poly_counts);
  qs->queen_list = QUEEN;
  qs->queen_polys = poly_counts[QUEEN];

  /* find a solution */
  go(qs);
}

ENTRYPOINT void draw_queens(ModeInfo *mi) 
{
  Queenscreen *qs = &qss[MI_SCREEN(mi)];
  Window w = MI_WINDOW(mi);
  Display *disp = MI_DISPLAY(mi);

  if(!qs->glx_context)
    return;

  glXMakeCurrent(disp, w, *qs->glx_context);

  if(flat)
    glShadeModel(GL_FLAT);
  
  clearbits = GL_COLOR_BUFFER_BIT;

  glColorMaterial(GL_FRONT, GL_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);

  if(!wire) {
    setup_lights(qs);
    glEnable(GL_DEPTH_TEST);
    clearbits |= GL_DEPTH_BUFFER_BIT;
    clearbits |= GL_STENCIL_BUFFER_BIT;
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
  }
  else
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

  mi->polygon_count = display(mi, qs);
  mi->recursion_depth = qs->BOARDSIZE;

  if(mi->fps_p) do_fps(mi);
  glFinish(); 
  glXSwapBuffers(disp, w);
}


ENTRYPOINT void free_queens(ModeInfo *mi) 
{
  Queenscreen *qs = &qss[MI_SCREEN(mi)];
  int i;
  if (!qs->glx_context) return;
  glXMakeCurrent (MI_DISPLAY(mi), MI_WINDOW(mi), *qs->glx_context);
  gltrackball_free (qs->trackball);

  /* this is horrible! List numbers are hardcoded! */
  for (i = 1; i <= 20; i++)
    if (glIsList(i)) glDeleteLists(i, 1);
}

XSCREENSAVER_MODULE ("Queens", queens)

#endif
