/*
 *  tracker/PatternTools.cpp
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

///////////////////////////////////////////////////////////////
// Handle with care
///////////////////////////////////////////////////////////////
#include "PatternTools.h"
#include "MilkyPlay.h"

static char* noteNames[12] = {(char*)"C-",(char*)"C#",(char*)"D-",(char*)"D#",(char*)"E-",(char*)"F-",(char*)"F#",(char*)"G-",(char*)"G#",(char*)"A-",(char*)"A#",(char*)"B-"};
static const char hex[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};

/*TXMPattern* PatternTools::pattern = NULL;
pp_int32 PatternTools::currentEffectIndex = 0;
pp_int32 PatternTools::offset = 0;

pp_int32 PatternTools::lockOffset = 0;*/

void PatternTools::switchSharpFlat() {
	if (strcmp(noteNames[1], "C#") == 0) {
		noteNames[1] = (char *) "Db";
		noteNames[3] = (char *) "Eb";
		noteNames[6] = (char *) "Gb";
		noteNames[8] = (char *) "Ab";
		noteNames[10] = (char *) "Bb";
	} else {
		noteNames[1] = (char *) "C#";
		noteNames[3] = (char *) "D#";
		noteNames[6] = (char *) "F#";
		noteNames[8] = (char *) "G#";
		noteNames[10] = (char *) "A#";
	}
}

void PatternTools::setPosition(TXMPattern* pattern, pp_uint32 channel, pp_uint32 row)
{
	if (channel >= pattern->channum ||
		row >= pattern->rows)
		offset = -1;
	else
	{
		pp_uint32 slotSize = pattern->effnum*2 + 2;
		
		offset = (slotSize*pattern->channum)*row + (channel*slotSize);
		
		PatternTools::pattern = pattern;
	}
}

pp_int32 PatternTools::getNote()
{
	if (offset < 0)
		return 0;
		
	return *(pattern->patternData + offset);
}

void PatternTools::setNote(pp_uint32 note)
{
	if (offset < 0)
		return;

	*(pattern->patternData + offset) = (pp_uint8)note;
}

pp_int32 PatternTools::getInstrument()
{
	if (offset < 0)
		return 0;

	return *(pattern->patternData + offset + 1);
}

void PatternTools::setInstrument(pp_uint32 instrument)
{
	if (offset < 0)
		return;

	*(pattern->patternData + offset + 1) = (pp_uint8)instrument;
}

void PatternTools::getFirstEffect(pp_int32& effect, pp_int32& operand)
{
	if (offset < 0)
	{
		effect = operand = 0;
		return;
	}

	currentEffectIndex = 0;
	
	effect = *(pattern->patternData + offset + 2 + currentEffectIndex*2);
	
	operand = *(pattern->patternData + offset + 2 + currentEffectIndex*2 + 1);
}

void PatternTools::getNextEffect(pp_int32& effect, pp_int32& operand)
{
	if (offset < 0)
	{
		effect = operand = 0;
		return;
	}

	currentEffectIndex++;
	
	effect = *(pattern->patternData + offset + 2 + currentEffectIndex*2);
	
	operand = *(pattern->patternData + offset + 2 + currentEffectIndex*2 + 1);
}

void PatternTools::setFirstEffect(pp_int32 effect, pp_int32 operand)
{
	if (offset < 0)
		return;

	currentEffectIndex = 0;
	
	*(pattern->patternData + offset + 2 + currentEffectIndex*2) = (pp_uint8)effect;
	
	*(pattern->patternData + offset + 2 + currentEffectIndex*2 + 1) = (pp_uint8)operand;
}

void PatternTools::setNextEffect(pp_int32 effect, pp_int32 operand)
{
	if (offset < 0)
		return;

	currentEffectIndex++;
	
	*(pattern->patternData + offset + 2 + currentEffectIndex*2) = (pp_uint8)effect;
	
	*(pattern->patternData + offset + 2 + currentEffectIndex*2 + 1) = (pp_uint8)operand;
}

void PatternTools::setEffect(pp_int32 currentEffectIndex, pp_int32 effect, pp_int32 operand)
{
	if (offset < 0)
		return;

	*(pattern->patternData + offset + 2 + currentEffectIndex*2) = (pp_uint8)effect;
	
	*(pattern->patternData + offset + 2 + currentEffectIndex*2 + 1) = (pp_uint8)operand;
}

void PatternTools::getEffect(pp_int32 currentEffectIndex, pp_int32& effect, pp_int32& operand)
{
	if (offset < 0)
	{	
		effect = operand = 0;
		return;
	}

	effect = *(pattern->patternData + offset + 2 + currentEffectIndex*2);
	
	operand = *(pattern->patternData + offset + 2 + currentEffectIndex*2 + 1);
}

void PatternTools::convertEffectsToFT2(pp_int32& eff, pp_int32& op)
{
	if (eff == 0 && op == 0)
		return;
	
	pp_uint32 effIn = eff;
	pp_uint32 opIn = op;	
	
	pp_uint32 effOut = 0;
	pp_uint32 opOut = 0;

	// Protracker commands
	if (effIn > 0 && effIn <= 0x11)
	{
		effOut = effIn;
		opOut = opIn;
	
		if (effIn == 0x0C || effIn == 0x10)
			opOut = (mp_ubyte)(((mp_sint32)opOut*64)/255);
	}
	// panning slide
	else if (effIn == 0x14 || effIn == 0x51)
	{
		effOut = 0x14;
		opOut = opIn;
	}
	// panning slide
	else if (effIn == 0x15)
	{
		effOut = effIn;
		opOut = opIn;
	}
	// set tempo
	else if (effIn == 0x16)
	{
		if (opIn)
		{
			if (opIn < 32) opIn = 32;
			effOut = 0x0F;
			opOut = opIn;
		}
	}
	// Panning slide / Multi retrig / tremor
	else if (effIn == 0x19 || effIn == 0x1B || effIn == 0x1D)
	{
		effOut = effIn;
		opOut = opIn;
	}
	// set speed
	else if (effIn == 0x1C)
	{
		if (opIn)
		{
			if (opIn > 31) opIn = 31;
			effOut = 0x0F;
			opOut = opIn;
		}
	}
	// MDL set sample offset
	else if (effIn == 0x1F)
	{
		effOut = 0x09;
		opOut = opIn;
	}	
	// PLM position jump
	else if (effIn == 0x2B)
	{
		effOut = 0x0B;
		opOut = opIn;
	}	
	// Protracker subcommands (most likely)
	else if (effIn >= 0x30 && effIn <= 0x3F)
	{
		effOut = 0x0E;
		opOut = ((effIn-0x30)<<4)+(opIn&0xF);
	}
	// arpeggio
	else if (effIn == 0x20)
	{
		if (opIn)
		{
			effOut = 0;
			opOut = opIn;
		}
			
	}
	// extra fine porta commands
	else if (effIn == 0x41)
	{
		effOut = 0x21;
		opOut = 0x10 + (opIn&0xF);
	}
	else if (effIn == 0x42)
	{
		effOut = 0x21;
		opOut = 0x20 + (opIn&0xF);
	}
	// MDL porta up
	else if (effIn == 0x43)
	{
		if (opIn >= 0xE0) {
			mp_ubyte y = (mp_ubyte)(opIn>>4);
			mp_ubyte x = (mp_ubyte)(opIn&0xf);
			switch (y) {
				case 0xF:
					effOut = 0xE;
					opOut = 0x10 + x;
					break;
				case 0xE: 
					effOut = 0x21;
					opOut = 0x10 + (x>>1);
					break;
			}
		}
		else if (opIn)
		{
			effOut = 0x1;
			opOut = opIn;
		}
	}
	// MDL porta down
	else if (effIn == 0x44)
	{
		if (opIn >= 0xE0) {
			mp_ubyte y = (mp_ubyte)(opIn >> 4);
			mp_ubyte x = (mp_ubyte)(opIn & 0xf);
			switch (y) {
				case 0xF:
					effOut = 0xE;
					opOut = 0x20 + x;
					break;
				case 0xE: 
					effOut = 0x21;
					opOut = 0x20 + (x>>1);
					break;
			}
		}
		else if (opIn)
		{
			effOut = 0x2;
			opOut = opIn;
		}
	}
	// MDL volslide up
	else if (effIn == 0x45)
	{
		if (opIn >= 0xE0) {
			mp_ubyte y = (mp_ubyte)(opIn>>4);
			mp_ubyte x = (mp_ubyte)(opIn&0xf);
			switch (y) {
				case 0xF:
					effOut = 0xE;
					opOut = 0xA0 + x;
					break;
				case 0xE: 
					effOut = 0xE;
					opOut = 0xA0 + (x>>2);
					break;
			}
		}
		else if (opIn)
		{
			effOut = 0xA;
			opOut = (opIn>>2)<<4;
		}
	}
	// MDL volslide down
	else if (effIn == 0x46)
	{
		if (opIn >= 0xE0) {
			mp_ubyte y = (mp_ubyte)(opIn >> 4);
			mp_ubyte x = (mp_ubyte)(opIn & 0xf);
			switch (y) {
				case 0xF:
					effOut = 0xE;
					opOut = 0xB0 + x;
					break;
				case 0xE: 
					effOut = 0xE;
					opOut = 0xB0 + (x>>2);
					break;
			}
		}
		else if (opIn)
		{
			effOut = 0xA;
			opOut = (opIn>>2)&0xF;
		}
	}
	// S3M porta up
	else if (effIn == 0x47) 
	{
		if (opIn >= 0xE0) {
			mp_ubyte y = (mp_ubyte)(opIn >> 4);
			mp_ubyte x = (mp_ubyte)(opIn & 0xf);
			switch (y) {
				case 0xF:
					effOut = 0xE;
					opOut = 0x10 + x;
					break;
				case 0xE: 
					effOut = 0x21;
					opOut = 0x10 + x;
					break;
			}
		}
		else if (opIn)
		{
			effOut = 0x1;
			opOut = opIn;
		}
	}
	// S3M porta down
	else if (effIn == 0x48) 
	{
		if (opIn >= 0xE0) {
			mp_ubyte y = (mp_ubyte)(opIn >> 4);
			mp_ubyte x = (mp_ubyte)(opIn & 0xf);
			switch (y) {
				case 0xF:
					effOut = 0xE;
					opOut = 0x20 + x;
					break;
				case 0xE: 
					effOut = 0x21;
					opOut = 0x20 + x;
					break;
			}
		}
		else if (opIn)
		{
			effOut = 0x2;
			opOut = opIn;
		}
	}
	// S3M volslide
	else if (effIn == 0x49) 
	{
		if (opIn) 
		{
			mp_ubyte y = (mp_ubyte)(opIn>>4);
			mp_ubyte x = (mp_ubyte)(opIn&0xf);
			
			if (!(x == 0xF && y)&&!(y == 0xF && x)) 
			{
				if (x && y) x = 0;
				
				if (y) 
				{					
					effOut = 0xA;
					opOut = y<<4;
				}
				else if (x) 
				{
					effOut = 0xA;
					opOut = x;
				}
			
			}
			else
			{
				if (!(x==0x0F && !y) && !(y==0x0F && !x)) 
				{
					if (x==0x0F)
					{
						effOut = 0xE;
						opOut = 0xA0+y;
					}
					else if (y==0x0F)
					{
						effOut = 0xE;
						opOut = 0xB0+x;
					}
				}
			}
		}
	}
	// PSM fine volslide up
	else if (effIn == 0x4B)
	{
		effOut = 0x0E;
		opOut = opIn>>2;
		if (opOut>0xF) opOut=0x0F;
		opOut+=0xA0;
	}
	// PSM fine volslide down
	else if (effIn == 0x4C)
	{
		effOut = 0x0E;
		opOut = opIn>>2;
		if (opOut>0xF) opOut=0x0F;
		opOut+=0xB0;
	}
	// PSM porta up
	else if (effIn == 0x4D)
	{
		effOut = 0x01;
		opOut = opIn>>2;
	}
	// PSM porta down
	else if (effIn == 0x4E)
	{
		effOut = 0x02;
		opOut = opIn>>2;
	}

	eff = effOut;
	op = opOut;

}

void PatternTools::convertEffectsFromFT2(pp_int32& eff, pp_int32& op)
{
	bool f = false;
	for (pp_uint32 i = 0; i < XModule::numValidXMEffects; i++)
		if (eff == XModule::validXMEffects[i]) 
			f = true;

	if (!f)
	{
		eff = op = 0;
		return;
	}

	if ((eff==0xC)||(eff==0x10)) 
	{
		op = XModule::vol64to255(op);
	}
	
	else if ((!eff)&&(op)) 
		eff=0x20;
	
	else if (eff==0xE) 
	{
		eff=(op>>4)+0x30;
		op=op&0xf;
	}
	
	else if (eff==0x21) 
	{
		eff=(op>>4)+0x40;
		op=op&0xf;
	}
	
}
	
pp_int32 PatternTools::getVolumeFromEffect(pp_int32 eff, pp_int32 op)
{
	
	mp_ubyte vol = 0;

	// set volume
	if (eff == 0x0C)
	{
		vol = (mp_ubyte)(0x10 + op);
	}
	// volslide
	else if (eff == 0x0A)
	{
		
		// use last operand?
		if (!op)
		{
			vol = 0x60;
		}
		// volslide down
		else if (op & 0xF)
		{
			vol = 0x60 + (mp_ubyte)(op&0xF);
		}
		// volslide up
		else if (op >> 4)
		{
			vol = 0x70 + (mp_ubyte)(op>>4);
		}
		
	}
	// extra fine volslide up
	else if (eff == 0xE && ((op>>4) == 0xA))
	{
		vol = 0x90 + (mp_ubyte)(op & 0xF);
	}
	// extra fine volslide down
	else if (eff == 0xE && ((op>>4) == 0xB))
	{
		vol = 0x80 + (mp_ubyte)(op & 0xF);
	}
	// extra vibrato
	else if (eff == 0x4)
	{
		if ((op>>4) && !(op&0xF))
		{
			vol = 0xA0 + (mp_ubyte)(op>>4);
		}
		else if (!(op>>4)/* && (op&0xF)*/)
		{
			vol = 0xB0 + (mp_ubyte)op;
		}
	}
	// set panning
	else if (eff == 0x8)
	{
		vol = 0xC0 + (mp_ubyte)(op>>4);
	}
	// panning slide
	else if (eff == 0x19)
	{
		// use last operand?
		if (!op)
		{
			vol = 0xD0;
		}
		// panning slide left
		else if (op & 0xF)
		{
			vol = 0xD0 + (mp_ubyte)(op&0xF);
		}
		// panning slide right
		else if (op >> 4)
		{
			vol = 0xE0 + (mp_ubyte)(op>>4);
		}
	}
	// porta to note
	else if (eff == 0x03)
	{
		vol = 0xF0 + (mp_ubyte)(op>>4);
	}
	
	return vol;
}

void PatternTools::getNoteName(char* name, pp_uint32 note, bool terminate /* = true*/)
{
	
	if (note == 0)
	{
		strcpy(name, "\xf4\xf4\xf4");
		return;
	}
	else if ( note >= getNoteOffNote())
	{
		strcpy(name, "\xf1\xf2\xf3");
		return;
	}

	memcpy(name, noteNames[(note-1)%12], 2);
	name[2] = '0' + (mp_ubyte)((note-1)/12);
	if (terminate)
		name[3] = 0;
}

void PatternTools::getVolumeName(char* name, pp_uint32 volume)
{
	if (volume<0x10)
	{
		name[0] = name[1] = '\xf4';
		name[2] = 0; 
	}
	else if (volume>=0x10 && volume<=0x50)
	{
		PatternTools::convertToHex(name, volume-0x10, 2);
	}
	else 
	{
		name[2] = 0; 
		name[1] = hex[volume&0xF];
		switch (volume>>4)
		{
			case 0x6:
				name[0] = '-';
				break;
			case 0x7:
				name[0] = '+';
				break;
			case 0x8:
				name[0] = (char)253;
				break;
			case 0x9:
				name[0] = (char)254;
				break;
			case 0xA:
				name[0] = 'S';
				break;
			case 0xB:
				name[0] = 'V';
				break;
			case 0xC:
				name[0] = 'P';
				break;
			case 0xD:
				name[0] = '<';
				break;
			case 0xE:
				name[0] = '>';
				break;
			case 0xF:
				name[0] = 'M';
				break;
		}
	}
}

void PatternTools::getEffectName(char* name, pp_uint32 effect)
{
	name[1] = 0;
	if (effect > sizeof(hex))
	{
		name[0] = (char)0xfa;
		return;
	}
	name[0] = hex[effect];	
}

pp_uint32 PatternTools::getHexNumDigits(pp_uint32 value)
{
	pp_uint32 i = 1;
	while ((value = value >> 4))
		i++;

	return i;
}

void PatternTools::convertToHex(char* name, pp_uint32 value, pp_uint32 numDigits)
{
	pp_uint32 i = 0;
	for (i = 0; i < numDigits; i++)
		name[i] = hex[((value>>(numDigits-1-i)*4)&0xF)];
	
	name[i] = 0;
}

pp_uint32 PatternTools::getDecNumDigits(pp_uint32 value)
{
	if (value == 0)
		return 1;

	pp_uint32 i = 0;
	pp_uint32 dec = 1;
	while (value / dec)
	{
		i++;
		dec*=10;
	}

	return i;
}

void PatternTools::convertToDec(char* name, pp_uint32 value, pp_uint32 numDigits)
{
	pp_int32 i = 0;
	pp_int32 dec = 1;
	for (i = 0; i < (signed)numDigits-1; i++)
		dec*=10;

	for (i = 0; i < (signed)numDigits; i++)
	{
		name[i] = hex[(value/dec)%10];
		dec/=10;
	}
	
	name[i] = 0;
}

void PatternTools::convertVolumeToEffect(pp_int32 vol, pp_int32& effect, pp_int32& operand)
{
	effect = operand = 0;

	if (vol>=0x10&&vol<=0x50) 
	{
		effect = 0x0C;
		operand = XModule::vol64to255(vol-0x10);
		return;
	}
	
	if (vol>=0x60) 
	{
		mp_ubyte eff = (mp_ubyte)(vol>>4);
		mp_ubyte op  = (mp_ubyte)(vol&0xf);
		
		switch (eff) 
		{
		case 0x6 : 
			eff=0x0A;
			op=op;
			break;
			
		case 0x7 : 
			eff=0x0A;
			op=op<<4;
			break;
			
		case 0x8 : 
			eff=0x3B;
			op=op;
			break;
			
		case 0x9 : 
			eff=0x3A;
			op=op;
			break;
			
		case 0xA : 
			eff=0x4;
			op=op<<4;
			break;
			
		case 0xB : 
			eff=0x4;
			op=op;
			break;
			
		case 0xC : 
			eff=0x8;
			op=op*0x11;
			break;
			
		case 0xD : 
			eff=0x19;
			op=op;
			break;
			
		case 0xE : 
			eff=0x19;
			op=op<<4;
			break;
			
		case 0xF : 
			eff=0x3;
			op=op<<4;
			break;
			
		}

		effect = eff;
		operand = op;

	}
	
}

pp_uint8 PatternTools::getNoteOffNote() { return XModule::NOTE_OFF; }
	
pp_uint32 PatternTools::normalizeVol(pp_uint32 volume) { return XModule::vol64to255((volume*64)/255); }

pp_uint8 PatternTools::getNibble(pp_int32 op, PatternEditor::NibbleTypes type)
{
    pp_uint8 value = 0;
    if (type == PatternEditor::NibbleTypeHigh) {
		value = (op >> 4) & 0x0F;
   } else if (type == PatternEditor::NibbleTypeLow) {
        value = op & 0x0F;
   }
   return value;
}

void PatternTools::getEffectDescription(char* label, char fxchar){
	switch( fxchar ){
		case '0': sprintf(label,"arp"); break; 
		case '1': sprintf(label,"portup"); break; 
		case '2': sprintf(label,"portdown"); break; 
		case '3': sprintf(label,"portnote"); break; 
		case '4': sprintf(label,"vibrato"); break; 
		case '5': sprintf(label,"portafade"); break; 
		case '6': sprintf(label,"vibrafade"); break; 
		case '7': sprintf(label,"tremolo"); break; 
		case '8': sprintf(label,"pan"); break; 
		case '9': sprintf(label,"smpstart"); break; 
		case 'A': sprintf(label,"volfade"); break; 
		case 'B': sprintf(label,"jump"); break; 
		case 'C': sprintf(label,"vol"); break; 
		case 'D': sprintf(label,"break"); break; 
		case 'E': sprintf(label,"subcmd"); break; 
		case 'F': sprintf(label,"bpm"); break; 
		case 'G': sprintf(label,"gvol"); break; 
		case 'H': sprintf(label,"gfade"); break; 
		case 'K': sprintf(label,"keyoff"); break; 
		case 'L': sprintf(label,"envpos"); break; 
		case 'P': sprintf(label,"panslide"); break; 
		case 'R': sprintf(label,"retrigfade"); break; 
		case 'T': sprintf(label,"tremor"); break; 
		case 'X': sprintf(label,"fineporta"); break; 
	}
}
