/*  Siteswap v1.2 beta 1
 *  Released on 01/13/02 by Nathan Peterson
 *
 *  juggle.c contains main code for ball movement and siteswap parsing
 *  Originally written by Glenn Hutchings, modified by Stuart Macmillan
 *  and Nathan Peterson
 *  Contact info: Nathan Peterson (nathan@juggler.net)
 *  Download the latest version at http://www.siteswap.org/palm/
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 */

/*
** PalmOS includes
*/
#include <PalmTypes.h>
#include <PalmCompatibility.h>
#include <Font.h>
#include <StringMgr.h>
#include <Window.h>
#include <SysUtils.h>
#include <MemoryMgr.h>
#include <stdio.h>
#include <System/ErrorMgr.h>

#include "globals.h"
#include "juggle.h"

/* Random numbers */
#define RANDINIT		SysRandom(0)
#define RANDOM(n)		(((long) SysRandom(0)) % (n))

#define M_PI        3.14159265358979323846	/* pi */
#define M_PI_2      1.57079632679489661923	/* pi/2 */


#define OFSCRN_WIN_Y 140
#define OFSCRN_WIN_X 130

extern float sin(float xx);
Boolean jug_setup(Char *);
void SSFLDUpd(CharPtr ss, int balls );
//void jug_clear();
void juggle_set_draw_front();
void juggle_set_draw_back();
void print_jug_data();

/* The main drawing effect */
#define X(t)		(sin(M_PI_2*(t)))
#define Y(t)		(4.0*(t)*(1.0-(t)))

#define LEFT_HAND(n)	((int)(n) % 2 == 0)
#define RIGHT_HAND(n)	((int)(n) % 2 != 0)

/* Throw height from a pattern position */
#define THROW(n)	pattern[(n) % period]
#define CATCH(n)	(n+THROW(n))%(juggle_cycle*2)

/* External variables (see juggle.h) */
char *juggle_pattern = NULL;
int juggle_nballs = 0;
int juggle_maxht = 0;
int juggle_cycle = 0;
int period ;
int *jug_initial;
int *jtime;
double juggle_time;
double t;

int counter;

Boolean juggle_print = false;

// globals
int *pattern;
int *balls;
double juggle_step = 0.2; //0.05;
char *results;


static Boolean jug_init_pattern();
static Boolean jug_chk_pattern();



void printwchar(int inyg, int cnt, int y){
  char string[8];
  int lenj;

        StrIToA( string, inyg);
        lenj = StrLen( string);
        WinDrawChars( string,lenj,10+(cnt*10),y);
}


/* jug_chk_pattern()
 * create and check pattern[]
 *******************************/
static Boolean jug_chk_pattern() {

  int i ,j, oldPeriod, *permute, *oldPattern;

  // save old pattern and period in case check fails
  oldPattern = pattern;
  oldPeriod = period;

  /* check period */
  period = (int) StrLen( (CharPtr) juggle_pattern);
  if (period < 1 || period > 20) {
	period = oldPeriod;
	return false;
  }

  /* check for special case all zeros */
  i=0;
  do {
	if(i >= period) {
	  period = oldPeriod;
	  return false;
	}
  } while (juggle_pattern[i++] == '0');


  /* allocate memory */
  permute = ALLOC(int, period);
  pattern = ALLOC(int, period);

  /* Build pattern */
  for (i = 0; i < period; i++) {
	if(juggle_pattern[i]>='0' && juggle_pattern[i]<='9')
	  pattern[i] = juggle_pattern[i] - '0';
	else if(juggle_pattern[i]>='a' && juggle_pattern[i]<='z')
	  pattern[i] = 10 + juggle_pattern[i] - 'a';
	else if(juggle_pattern[i]>='A' && juggle_pattern[i]<='Z')
	  pattern[i] = 10 + juggle_pattern[i] - 'A';
	else
	  goto Check_fail;
	permute[i] = ( pattern[i] + i ) % period;
  }

  /* Check if valid */
  for (i = 0; i < period; i++) {
	for (j = 0; j < i; j++) {
	  if (permute[i] == permute[j])
		goto Check_fail;
	}
  }

  /* valid pattern. clean up memory and keep new pattern */
  DEALLOC(permute);
  DEALLOC(oldPattern); //**
  return true;

/* not a valid pattern. clean up mem and restore old pattern */
Check_fail:
  DEALLOC(permute);
  DEALLOC(pattern);
  pattern = oldPattern; // restore old pattern
  period = oldPeriod;	// and period
  return false;
}

/* numballs()
 * used in jug_random to determine the number of balls in a pattern
 *****************************************************************/
int numballs(int* patt,int period){
	int i, nballs;

	nballs = 0;
	for(i=0;i<period;i++){
		nballs += patt[i];
	}

	nballs /= period;

	return nballs;
}


/* jug_random()
 * generates a random pattern of length 6 and calls jug_setup()
 *****************************************************************/
void jug_random() {

      int i,j,rndperiod,rndnumballs,iterations;
      int *rndpattern;
      CharPtr jug_rndpattern = NULL;
	/* Random pattern */
	int nswaps = 50;

//      jug_clear();

    rndperiod = RANDOM(9)+1;   // 0 < random period < 10
    rndnumballs = RANDOM(5)+3; // 2 < random period < 8


	rndpattern = ALLOC(int, rndperiod);

	/* Create random permutation */
	for (i = 0; i < rndperiod; i++)
	    rndpattern[i] = i;

  	while (nswaps-- > 0) {
  	    i = RANDOM(rndperiod);
  	    j = RANDOM(rndperiod);
  	    SWAP(rndpattern[i], rndpattern[j]);
  	}

  	/* Convert it to pattern */
  	jug_rndpattern = ALLOC(char, rndperiod+1);

  	for (i = 0; i < rndperiod; i++) {
  	    rndpattern[i] -= i;
  	    while (rndpattern[i] < 0)
  		   rndpattern[i] += rndperiod;
//  	    if (rndpattern[i] == 0 && RANDOM(2))
//  		rndpattern[i] += rndperiod;
  	}

  	// ensure correct number of balls
  	iterations = 0;
  	while(numballs(rndpattern, rndperiod) != rndnumballs){
		if(++iterations > 50) { break; }  // safety precaution
		if(numballs(rndpattern, rndperiod) < rndnumballs){
           i = RANDOM(rndperiod);
           if(rndpattern[i] + rndperiod <= 14) // make sure this number is at least 2 times max # of balls
              rndpattern[i] += rndperiod;
        } else if(numballs(rndpattern, rndperiod) > rndnumballs){
           i = RANDOM(rndperiod);
           if(rndpattern[i] - rndperiod >= 0)
              rndpattern[i] -= rndperiod;
	    }
	}

	// transfer array to string
	for(i = 0; i < rndperiod; i++) {
		if(rndpattern[i] > 9)
		   jug_rndpattern[i] = 'a' + rndpattern[i] - 10;
		else
  	       jug_rndpattern[i] = '0' + rndpattern[i];
	}

  	jug_rndpattern[rndperiod] = '\0';
        DEALLOC(rndpattern);

	StrCopy(wchItmString, jug_rndpattern);
	wchItm = wchItmString;
    jug_setup(wchItmString);
    SSFLDUpd(jug_rndpattern, juggle_nballs);
    DEALLOC(jug_rndpattern);

}


/* jug_clear() (not used)
 * dealloc pattern[] and balls[] and clear associated vars
 ************************************************************/
/*void jug_clear(){

        juggle_end();
        juggle_nballs = 0;
        juggle_maxht = 0;
        juggle_cycle = 0;
        period = 0;
	DEALLOC(pattern);
	DEALLOC(balls);
}*/


/* jug_delete_spaces()
 * deletes all spaces in a given string
 ******************************************************/
CharPtr jug_delete_spaces(CharPtr dst, CharPtr src)
{
	while(*src != '\0'){
		if(*src == ' '){ src++; }
		else { *dst++ = *src++; }
	}
	*dst = '\0';

	return dst;
}

/* jug_setup()
 * create pattern[], balls[], and initialize display
 ******************************************************/
Boolean jug_setup( CharPtr jug_new_pattern )
{

  juggle_pattern = ALLOC(char,StrLen(jug_new_pattern)+1);
  jug_delete_spaces(juggle_pattern, jug_new_pattern);


//  jug_set_step( DEFAULT_STEP , 0);

  if (!jug_chk_pattern()) {return false;}	// create pattern[]
  jug_init_pattern();						// create balls[]
  juggle_init();	/* jug_initialize display */

  if (pref_Sound) {		/* play initialize sounds */
	JuggleSounds(1500);
	JuggleSounds(800);
	JuggleSounds(100);
  }

  // free memory
  DEALLOC(juggle_pattern);
  juggle_pattern = jug_new_pattern; // point pointer here just in case

  return true;
}

void print_jug_data() {

    int i;
  RectangleType clrit = { 0, 0, OFSCRN_WIN_X, OFSCRN_WIN_Y };
    juggle_set_draw_front();


  WinEraseRectangle(&clrit ,0);
     FntSetFont( stdFont );

#define DRAWY 30

//  WinSetDrawWindow( 0 );

        WinDrawChars( "Pattern:",8,10,DRAWY);
        WinDrawChars( juggle_pattern,period,60,DRAWY);
        WinDrawChars( "Period:",7,10, DRAWY+10);
        printwchar( period,5,DRAWY+10);

        WinDrawChars( "Max. Hgt",8,10,DRAWY+20);
        printwchar( juggle_maxht,5,DRAWY+20);
        WinDrawChars( "Cycle:",6,10,DRAWY+30);
        printwchar( juggle_cycle,5,DRAWY+30);

        WinDrawChars( "Num Balls:",9,10,DRAWY+40);
        printwchar( juggle_nballs,5,DRAWY+40);

        WinDrawChars( "Balls[]:",8,5,DRAWY+60);
    for (i = 0; i < juggle_nballs; i++) {
            printwchar( balls[i],(i*2)+3,DRAWY+60);
            printwchar( THROW(balls[i]),(i*2)+3,DRAWY+70);
	}
//WinEraseRectangle(&clrit ,0);

    juggle_set_draw_back();

};

/* jug_init_pattern()
 * create and init balls[]
 ****************************/
static Boolean jug_init_pattern(){

  int i,j;

  /* Set up pattern */

  juggle_maxht = 0;
  juggle_cycle = 0;
  /* Find maximum throwing height and pattern length */
  for (i = 0; i < period; i++) {
	juggle_maxht = max(pattern[i], juggle_maxht);
	juggle_cycle += pattern[i];
  }


  /* initialize balls */

  juggle_nballs = juggle_cycle / period;
  DEALLOC(balls);
  balls = ALLOC(int, juggle_nballs);

  /* Find the start times for each ball */
  /* Note, for simple patterns start times are obvious (eg. 441 -> 012)	*/
  /* but for a different eg, 620235 has start times: balls[]=015			*/

  jug_initial = ALLOC(int, juggle_cycle); // indicates previous ball thrown at that time
  for (j = 0; j < juggle_cycle; j++)
	jug_initial[j] = 0;

  for (i = j = 0; i < juggle_nballs; i++) {	// j=time, i=ball num
	while(jug_initial[j] != 0 || THROW(j)==0){ j++; }
	  balls[i]=j;					// ball i is first thrown at time j
	  while(j < juggle_cycle){		// mark all throws by this ball within cycle
		jug_initial[j]=1;
		j += THROW(j);
	  }
	j=balls[i]+1;
  }

  DEALLOC(jug_initial);
  juggle_time=0;
  return true;
} /* everything set up now */


/* jug_continue()
 * this is the main code responsible for the animation
 * it updates the ball positions, and then increments juggle_time
 *******************************************************************/
Boolean jug_continue(){

  int i;
  float x,y;

  /* Juggle them balls! */

  /* Set up next frame */
  juggle_frame();


  /* Draw balls on it */
  for (i = 0; i < juggle_nballs; i++) {
	int throw, catch;// , thrchk;

	throw = balls[i];
	catch = CATCH(throw);

	/* Find throw and catch times */
	if (juggle_time >= catch && juggle_time-juggle_step < catch){

	  if (pref_Sound)// && (THROW(thrchk) == juggle_maxht) )
		JuggleSounds( 200 );

	  throw = balls[i] = catch;
	  catch = CATCH(throw);

	}

	// Calculate unit time since throw
	if (juggle_time >= throw)
	  t = (juggle_time - throw) / (THROW(throw));
	else
	  t = ((juggle_time + juggle_cycle*2) - throw) / (THROW(throw));
//	else // ball has not been thrown ever
//	  t = 0;

	/* Calculate x/y position in unit box */
	if (LEFT_HAND(throw))
	  x = (LEFT_HAND(catch) ? 0.0 : X(t));
	else
	  x = (LEFT_HAND(catch) ?  1.0-X(t) : 1.0 );

	if (pref_Hold && (THROW(throw) == 2) )
	  y = 0.0;
	else
	  y = Y(t) * THROW(throw) / juggle_maxht;


	// if ball has been thrown at least once, then put the ball there
	if (juggle_time >= throw || throw > catch) {
	  juggle_ball(i, throw, catch, x, y);
	}

  }


  /* Update display */
  juggle_update();

//        if ( juggle_print )
//                 print_jug_data();

  /* Next timestep */
  juggle_time = juggle_time + juggle_step;
  if (juggle_time >= juggle_cycle*2) {juggle_time = juggle_time-juggle_cycle*2;}

  return true;
}


/* Set the juggle timestep if not already done */
void jug_set_step(float setr, int force)
{
    if (juggle_step < 0.0 || force)
	juggle_step = setr;

    /* Silently enforce limits */
    juggle_step = min(juggle_step, 1.0);
    juggle_step = max(juggle_step, 0.001);

}

