#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ce478.h"
#include "data.h"

//need a line delay of 5ms on a line feed

#define COVERED 4
#define TUNING tuning_standard_e
#define DEBUG 1

struct time_event* events_begin = NULL;
struct time_event* events_at = NULL;
unsigned long notes_total = 0;
unsigned long notes_skipped = 0;
unsigned long events_total = 0;
unsigned long events_skipped = 0;
unsigned long tempo = 0;
char note_min = 200;
char note_max = 0;
int drop = 0;

const char str[] = TUNING;

#define LVL1	0x0C00
#define PISCR	(* (volatile unsigned short*) 0x2fc240)	//16 bits
#define PITC	(* (volatile unsigned short*) 0x2fc244)	//16 bits
#define PITR	(* (volatile unsigned short*) 0x2fc248) //16 bits
#define SIMASK	(* (volatile unsigned short*) 0x2FC014)	//16 bits
#define SIVEC	(* (volatile unsigned char*) 0x2FC01C) //8  bits

#define MIOS1ER0 (* (volatile unsigned short*) 0x306C04)

#define MPIOSMDR  (* (volatile unsigned short*) 0x306100)
#define MPIOSMDDR (* (volatile unsigned short*) 0x306102)

#define PQADR (* (volatile unsigned char*) 0x304806)

#define PQADDR (* (volatile unsigned char*) 0x304808)

#define MPWMSM0CR (* (volatile unsigned short*) 0x306006)
#define MPWMSM1CR (* (volatile unsigned short*) 0x30600E)
#define MPWMSM2CR (* (volatile unsigned short*) 0x306016)
#define MPWMSM3CR (* (volatile unsigned short*) 0x30601E)
#define MPWMSM16CR (* (volatile unsigned short*) 0x306086)
#define MPWMSM17CR (* (volatile unsigned short*) 0x30608E)

/*
Table 15-26 MPIOSMDR Bit Descriptions
Bit(s) Name Description
0:15 D[15:0]
Data BITS. These bits are read/write data bits that define the value to be driven to the pad in
output mode for each implemented I/O pin of the MPIOSM. While in output mode, a read returns
the value of the pad. Note that, when little-endian bit ordering is used, bit 0 corresponds to D15
and bit 15 corresponds to D0.
*/


/* register pin mapping

vpin	register     actual pin		guitar

0		PQA 0		 AN52           low e strum	
1		PQA 1		 AN53			a strum
2		PQA 2		 AN54			d strum
3		PQA 3		 AN55			g strum
4		PQA 4 		 AN56			b strum
5		PQA 5 		 AN57			e strum
6		PQA 6		 AN58			e1
7		PQA 7		 AN59			e2
8		MPIOSMDR[0]	 MIOS19         e3
9		MPIOSMDR[1]	 MIOS20			e4
10		MPIOSMDR[2]  MIOS21			a1
11		MPIOSMDR[3]  MIOS22			a2
12		MPIOSMDR[4]	 MIOS23			a3
13		MPIOSMDR[5]  MIOS24			a4
14		MPIOSMDR[6]  MIOS25			d1
15		MPIOSMDR[7]  MIOS26			d2
16		MPIOSMDR[8]  MIOS27			d3
17		MPIOSMDR[9]  MIOS28			d4
18		MPIOSMDR[10] MIOS29			g1
19		MPIOSMDR[11] MIOS30			g2
20		MPIOSMDR[12] MIOS31			g3	
21		MPIOSMDR[13] MIOS32			g4
22		MPIOSMDR[14] MIOS33			b1
23		MPIOSMDR[15] MIOS34			b2
24	    MPWMSM0CR    MIOS11			b3
25		MPWMSM1CR	 MIOS12			b4
26		MPWMSM2CR	 MIOS13			e1
27		MPWMSM3CR  	 MIOS14			e2	
28		MPWMSM16CR   MIOS15			e3
29		MPWMSM17CR	 MIOS16			e4


E  26  27  28  29
B  22  23  24  25 
G  18  19  20  21
D  14  15  16  17
A  10  11  12  13
E  6   7   8   9



*/

void inithw();
struct time_event* setup(FILE*);
void display();
struct coord* getCoord(char,char);
void fillInNotes(struct time_event*);
void removeEmptyTimeEvents(struct time_event*);
void stats();
void timerinit();
void setPIT(unsigned long);
void pluck_coord(struct coord*);
void ptc_handler();
void setup_control_registers();
void reset_reg();
void pluck_vpin( int);
void strum_string(int);

void stats()
{
	printf("%i events with %i notes\n\r", events_total, notes_total);
	printf("Note range\t= [%i,%i]\n\r", note_min,note_max);
	printf("Guitar range\t= [%i,%i]\n\r", str[0], str[5]);
	printf("Skipping %3.2f%% of the notes.\n\r", 100*(float)notes_skipped/notes_total);
	printf("Skipping %3.2f%% of the events.\n\r", 100*(float)events_skipped/events_total);
	fflush(stdout);
}

struct time_event* setup(FILE* fin)
{
	unsigned long start_time=0,last_time = (unsigned long)-1;
	int len=0, stillgo = 1;
	char note=0;
	struct time_event* t = NULL;
	struct note_node* n = NULL;

/*
	file structure:
	
	tempo
	drop
	starttime	length	note
	.
	.
	.
	-1	-1	-1
*/

	char a[16], b[16], c[16];

	fscanf(fin, "%s", &a);
	tempo = strtoul(a, NULL, 10);
	tempo /= 100;
	
	fscanf(fin, "%s", &a);
	drop = atoi(a);

    while(stillgo)
	{
		fscanf(stdin, "%s", &a);
		fscanf(stdin, "%s", &b);
		fscanf(stdin, "%s", &c);
		stillgo = strcmp(a, "-1");
		
		if(!stillgo) 
		{
			continue;
		}
		
		start_time = strtoul(a, NULL, 10);
		len=atoi(b);
		note=(char)atoi(c);

		note+=(drop*12);
		if(DEBUG) { printf("\n\rNote number %ld: %ld|%i|%i", notes_total, start_time,len,note); }

		notes_total++;
		if(note<note_min) { note_min = note; }
		if(note>note_max) { note_max = note; }
		n = (struct note_node*) malloc(sizeof(struct note_node) * 1);
		n->length = len;
		n->note = note;
		n->c = NULL;
		n->next = NULL;

		if(start_time != last_time)
		{
			events_total++;
			t = (struct time_event*) malloc(sizeof(struct time_event) * 1);
			t->start_time = start_time;
			t->node = n;
			t->next = NULL;
		
			if(events_begin==NULL)
			{
				events_begin = t;
			}
			else
			{
				fillInNotes(events_at);
				events_at->next = t;
			}

			events_at = t;
		}
		else
		{
			struct note_node* tmp = events_at->node;
			while(tmp->next)
			{
				tmp=tmp->next;
			}
			tmp->next = n;
		}
		last_time = start_time;
	}
	
	fillInNotes(events_at);
	removeEmptyTimeEvents(events_begin);

	return events_begin;
}

void display()
{
	struct note_node* a = NULL;

	events_at = events_begin;

	while(events_at)
	{
		printf("\n\rstart time=%ld\n\r", events_at->start_time);
		a = events_at->node;
		while(a)
		{
			printf("\t(%i,%i)", a->c->string, a->c->fret);
			a=a->next;
		} 
		events_at = events_at->next;
	}
	puts("\n\r");
	fflush(stdout);
}

struct coord* getCoord(char note,char min_str)
{
	struct coord* ret = (struct coord*) malloc(sizeof(struct coord)*1);
	char i, winner=0;

	for(i=min_str;i<6;i++)
	{
		if(str[i]<=note && note<=str[i]+COVERED)
		{
			ret->string = i;
			ret->fret = note-str[i];
			winner=1;
			break;
		}
	}

	if(!winner) { free(ret); ret=NULL; }
	return ret;
}

void fillInNotes(struct time_event* ptr)
{
	int strings_played[] = {0,0,0,0,0,0};
	struct note_node* nn = ptr->node;
	struct note_node* nn_prev = NULL;
	struct coord* tmp;
	struct note_node* freeme;
	char tmpstr = 0;

	if(!ptr) { return; }

	while(nn)
	{
		tmp = getCoord(nn->note, tmpstr);

		if(tmp)
		{
			if(strings_played[tmp->string])
			{
				tmpstr=(tmp->string+1);
				free(tmp);
				continue;
			}
			else
			{
				strings_played[tmp->string] = 1;
				nn->c = tmp;
			}
		}
		else
		{
			if(DEBUG) { printf("\n\r\tSkipping note %i played at time %i", nn->note, ptr->start_time); }
			notes_skipped++;
			if(nn_prev)
			{
				nn_prev->next = nn->next;
			}
			else
			{
				ptr->node = nn->next;
			}
			freeme = nn;
			nn=nn->next;
			free(freeme);
			tmpstr=0;
			continue;
		}
		tmpstr=0;
		nn_prev=nn;
		nn=nn->next;
	}
}

void removeEmptyTimeEvents(struct time_event* test_node)
{
	struct time_event* last_good_node = NULL;

	while(test_node)
	{
		if(!test_node->node)
		{
			events_skipped++;
			if(DEBUG) { printf("\n\r\tDeleting time event %i", test_node->start_time); }
			if(last_good_node)
			{
				last_good_node->next = test_node->next;
			}
			else
			{
				events_begin = test_node->next;
			}
			free(test_node);
		}
		else
		{
			last_good_node=test_node;
		}

		if(last_good_node)
		{
			test_node=last_good_node->next;
		}
		else
		{
			test_node=events_begin;
		}
	}
}

void timerinit()
{
	//here enable pit timer, set the first count to be first event
	
	//disable counting down by setting PTE to 0 and
	//disable int by setting PIE to 0
	//set everything to 1 (to set PS to 0) then set everything to 0
  	PISCR = 0xFFFF;
  	PISCR = 0x0000;
  	
  	//set the counter  	
  	setPIT(events_begin->start_time);
  	
  	//set pirq to LVL1
  	PISCR = LVL1;
  	
  	//set up SIMASK
  	//this doesnt work like the man page says it should
	SIMASK = 0x00F0;
	
   	//enable int gen by setting PIE to 1
   	PISCR |= 0x0004; 	
   	//enable counting down by setting PTE to 1
   	PISCR |= 0x0001; 		
}

//take the time delta, maybe have to modulus this if too big? handle that in setPIT ???
void setPIT(unsigned long milliseconds)
{
	//milliseconds*=tempo;
	PITC = milliseconds*((1000)/64);
}

/* 
the follow 'pin's are virtual, and they're true register and pin mappings are above.
each set of consecutive pins are for consective frets on strings ascending
i.e, 6,7,8,9 = first, second, third, fourth frets on the first* string,
however the first string is indexed at zero.
The equation for calculated the pin based on string and fret are as follows
pin = 4(string) + fret + 5 
E  26  27  28  29
B  22  23  24  25 
G  18  19  20  21
D  14  15  16  17
A  10  11  12  13
E   6   7   8   9
*/

void reset_reg()
{
	PQADR = 0x00;
	MPIOSMDR = 0x0000;
	
	MPWMSM0CR = 0x4000;
	MPWMSM1CR = 0x4000;
	MPWMSM2CR = 0x4000;
	MPWMSM3CR = 0x4000;
	MPWMSM16CR = 0x4000;
	MPWMSM17CR = 0x4000;
}

void pluck( struct coord* c)
{
	unsigned int temp =0;
	unsigned char tmp2 =0;
	unsigned int pin=0;
	unsigned int p=0;

	reset_reg();
	
	printf("\n\rPlucking %i:%i", c->string, c->fret);
	
	if(c->fret != 0)
	{
		pin = (4*c->string);		
		pin += c->fret;
		pin += 5;
		p = pin;
		
		//fosterrr's great if
		if( p ==6 || p ==7 ) // pqa 6,7
		{
			if (p==6)
				PQADR = 0x7f;
			if (p==7)
				PQADR = 0xdf;
		}
		
		if( p > 7 && p < 24)   //miosgpio
		{
			pin -= 8;
			temp = 0x1;
			temp <<= pin;
			MPIOSMDR = temp;
		}
		
		if(p > 23 ) 
		{
			switch(pin)
			{
				case 24:
					MPWMSM0CR = 0x4800;
					break;
				case 25:
					MPWMSM1CR = 0x4800;
					break;
				case 26:
					MPWMSM2CR = 0x4800;
					break;
				case 27:
					MPWMSM3CR = 0x4800;
					break;
				case 28:
					MPWMSM16CR = 0x4800;
					break;
				case 29:
					MPWMSM17CR = 0x4800;
					break;
				default:
					break;
			}
		}

	}

	tmp2= 0x1;
	tmp2 <<= (c->string);
	PQADR |= tmp2;

}

void pluck_coord( struct coord* c)
{
	int vpin;
	reset_reg();
	
	vpin = 4*c->string + c->fret + 5;
	
	if(c->fret)
	{
		pluck_vpin(vpin);
	}
	
	strum_string(c->string);
}


void pluck_vpin( int vpin)
{
	unsigned int temp=0;
	int p = vpin, pin=vpin;

	if (p==6)
	{
		PQADR = 0x7f;
	}
		
	if (p==7)
	{
		PQADR = 0xdf;
	}
		
	if( p > 7 && p < 24)   //miosgpio
	{
		pin -= 8;
		temp = 0x1;
		temp <<= pin;
		MPIOSMDR = temp;
	}
		
	if(p > 23 ) 
	{
		switch(pin)
		{
			case 24:
				MPWMSM0CR = 0x4800;
				break;
			case 25:
				MPWMSM1CR = 0x4800;
				break;
			case 26:
				MPWMSM2CR = 0x4800;
				break;
			case 27:
				MPWMSM3CR = 0x4800;
				break;
			case 28:
				MPWMSM16CR = 0x4800;
				break;
			case 29:
				MPWMSM17CR = 0x4800;
				break;
			default:
				break;
		}
	}
}

void strum_string(int string)
{
	//uses PQA 0-5
	char old = PQADR;
	char tmp= 0;
	int i=0;
	int j=0;

	switch(string)
	{
		case 6: tmp=1; break;
		case 5: tmp=2; break;
		case 4: tmp=4; break;
		case 3: tmp=8; break;
		case 2: tmp=16; break;
		case 1: tmp=32; break;
	}
	
	PQADR |= tmp;
/*	
	for(i=0;i<1000;i++)
	{
		j++;
	}
	
	PQADR = old;
*/
}

void ptc_handler()
{
	struct note_node* n = NULL;

	//were taking the int, clear it
	PISCR |= 0x0080;

	//iterate through this time event, pluck strings
	n = events_at->node;
	
	while(n)
	{
		pluck(n->c);
		n= n->next;
	}
	
	//set the timer
	setPIT(events_at->next->start_time - events_at->start_time);
	
	//increment events ptr
	events_at = events_at->next;
	
	//test for eof, stop interrupts if last
	if(events_at == NULL)
	{
		printf("\n\rDone");
		//disable int gen by setting PIE to 0
   		PISCR &= 0xFFFB; 	
   		//disable counting down by setting PTE to 0
   		PISCR &= 0xFFFE;
   		//stop listening for ints, undo what we did
   		SIMASK &= 0xFF0F;
   		reset_reg();
	}
}

void setup_control_registers()
{

	MIOS1ER0 =0x0000;
	MPIOSMDDR = 0xFFFF; //set all MGPIO to output
	PQADDR = 0xff;
}

void main()
{
	struct coord c;

	inithw();

	setup_control_registers();
	reset_reg();

	puts("\n\r");
	fflush(stdout);

	events_begin = setup(stdin);
	fflush(stdout);

	stats();
	
	events_at = events_begin;

	//set the timers and timers, activate!
	timerinit();

	//set the external int enable
	asm("mtspr EIE, r3");

	//let the timers take over
	while(1)
	{
		//Mr. Crowley, won't you ride my white horse?
	}
}




#pragma force_active on
#pragma interrupt on
#pragma section code_type ".abs.fff00500"

void Ext_Isr()
{
	ptc_handler();
}
