/* ======================
     zmd2mid テンポ関係
   ====================== */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "structs.h"
#include "VarLen.h"
#include "etc.h"

#include "config.h"
void setTempoRoundMode(int var);
int tempochg(UBYTE *MTrk,DWORD *gate_old,DWORD tempo);
void do_tempo(UBYTE **xxMTrk,DWORD tempos, TEMPODAT *tempo,
		DWORD syncs,SYNCDAT *sync,BYTE timer);
DWORD skipevent(UBYTE *MTrk);


static int roundMode = 0;

void setTempoRoundMode(int var)
{
	roundMode = var;
}


int tempochg(UBYTE *MTrk,DWORD *gate_old,DWORD tempo)	/* テンポ変更 */
{
	DWORD val;
	UBYTE *MTrk_ = MTrk;

	if (tempo < 30 && 300 < tempo) {
		fatal(1,"\nError: illegal tempo value is specified.\n");
	}
	MTrk += PutVarLen(MTrk,*gate_old);	/* delta */
	*MTrk++ = 0xFF;				/* set tempo */
	*MTrk++ = 0x51;
	*MTrk++ = 0x03;
	val = (roundMode)?
			(DWORD)(600000000L/tempo+5)/10 :
			(DWORD)(60000000L / tempo);
	*MTrk++ = (val&0xFF0000)/0x10000;
	*MTrk++ = (val&0x00FF00)/0x100;
	*MTrk++ = (val&0x0000FF);
	*gate_old=0;

	return MTrk - MTrk_;
}


void do_tempo(UBYTE **xxMTrk,DWORD tempos, TEMPODAT *tempo,
		DWORD syncs,SYNCDAT *sync,BYTE timer)
{
	UBYTE	*MTrk = *xxMTrk;	/* XXX: modified: thanks to E.Tanaka */
	DWORD i,j,temponow = 120,timernow = 0,totalbytes, totaldelta = 0;
	UBYTE *FF2Fpos , *MTrkTrk;	/* = &MTrk[8]; */
#ifdef __GCC__
	DWORD abstimesort[tempos];
#else
	DWORD *abstimesort;
#endif
#ifdef DEBUG
	FILE *fp;
#endif

#ifndef __GCC__
	abstimesort = (DWORD*)emalloc(sizeof(DWORD) * tempos, "abstimesort");
#endif
	/* totalbytes = *(int *)(MTrk + 4); */		/* テンポ挿入位置確保 */
	totalbytes = getDwordAlign(MTrk + 4);		/* テンポ挿入位置確保 */
	MTrk = (UBYTE *)erealloc((void*)MTrk,totalbytes + tempos * 6 + 1024, "Mtrk");
	*xxMTrk = MTrk;		/* XXX: modified */
	MTrkTrk = &MTrk[8];

	while (1) {
		DWORD delta , bytes;
		DWORD deltabytes = GetVarLen(MTrkTrk,&delta);
		MTrkTrk    += deltabytes;
		totaldelta += delta;
		bytes       = skipevent(MTrkTrk);
		if (bytes < 0) {		/* trk end */
			FF2Fpos     = MTrkTrk - deltabytes;
			totaldelta -= delta;
			break;
		} else {
			MTrkTrk += bytes;
		}
	}


#ifdef AAa
#ifdef UNIXY_OS
	fprintf(stderr,"\nSetting tempo.");
#else
	fprintf(stderr,"\nSetting tempo = \x1b[s");
#endif
#endif
	for (j = 0; j < tempos; j++) {
		DWORD reltime = 0;

		for (i = 0; i < syncs; i++) {	/* 以下、同一trkで同期受信してないか探す */
			if (sync[i].trk == tempo[j].trk && sync[i].midc >= 0) {
				if ( sync[i].total > tempo[j].total ||
					(sync[i].total == tempo[j].total &&  /*同一クロック時*/
					 sync[i].count >  tempo[j].count)) { /*MMLの前後判定*/
						break;
				} else {
					reltime = (sync[i].offset < 0)?		/* 安全装置 */
						0 : sync[i].offset - sync[i].total;
				}
			}
		}

		abstimesort[j] = tempo[j].total + reltime;	/* テンポ設定の絶対時間 */
	}

	for (j = 1; j < tempos; j++) {		/* 挿入ソート(安定) */
		DWORD i = j - 1;
		const DWORD key = abstimesort[j];
		const TEMPODAT key_tempo = tempo[j];

		while (i >= 0 && abstimesort[i] > key) {
			abstimesort[i + 1] = abstimesort[i];
			tempo[i + 1] = tempo[i];
			i--;
		}
		abstimesort[i + 1] = key;
		tempo[i + 1] = key_tempo;
	}

	for (j = 0; j < tempos; j++) {		/* 以下テンポイベントの挿入位置を決定する */
		DWORD abstime = abstimesort[j] , total = 0 , bytes;
		UBYTE *MTrkTrk = &MTrk[8];
		DWORD delta,newdelta,deltabytes,newdeltabytes,tempobytes;
		const DWORD tmp = tempo[j].value;

		totalbytes = getDwordAlign(MTrk + 4);
#ifdef AAA
#ifndef UNIXY_OS
		fprintf(stderr,"%ld/%ld\x1b[u",j + 1,tempos);
#endif
#endif
		if (abstime > totaldelta) {
			MTrkTrk    = FF2Fpos;
			total      = totaldelta;
			totaldelta = abstime;
		} else {
			while (1) {
				deltabytes = GetVarLen(MTrkTrk,&delta);
				if (total + delta > abstime) {
					break;
				}
				MTrkTrk += deltabytes;
				total   += delta;
				bytes   =  skipevent(MTrkTrk);
/* printf("skip bytes=%d\n",bytes); */
				if (bytes < 0) {		/* trk end */
					MTrkTrk -= deltabytes;
					total -= delta;
					break;
				} else {
					MTrkTrk += bytes;
				}
			}

			while (1) {				/* delta time 0 skip */
				/* deltabytes = */
				GetVarLen(MTrkTrk,&delta);
				if (delta != 0) {
					break;
				}
				/* MTrkTrk += deltabytes; */
				MTrkTrk++;
				/* total   += delta; */
				bytes   =  skipevent(MTrkTrk);
/* printf("Skip bytes=%d\n",bytes); */
				if (bytes < 0) {		/* trk end */
					/* MTrkTrk -= deltabytes; */
					MTrkTrk--;
					/* total -= delta; */
					break;
				} else {
					MTrkTrk += bytes;
				}
			}
		}

					/* 以下、その位置にテンポイベントを挿入 */
		delta = abstime - total;
		tempobytes = get_VarLen_bytes(delta) + 6;
		memmove(MTrkTrk + tempobytes,
			MTrkTrk,
			totalbytes + 8 - (MTrkTrk - &MTrk[0]));
		totalbytes += tempobytes;
		FF2Fpos    += tempobytes;		/* 最終位置の修正前半 */

		switch(tempo[j].attribute) {
			case 0:				/* 絶対、t */
				temponow = tmp;
				timernow = (timer==0)?
						 256 - 78125 / tmp / 16 :
						1024 - 78125 / tmp;
				break;
			case 1:				/* 相対、t */
				temponow += tmp;
				timernow = (timer==0)?
						 256 - 78125 / tmp / 16 :
						1024 - 78125 / tmp;
				break;
			case 2:				/* 絶対、@t */
				timernow = tmp;
				temponow = !timer?
					78125 / 16 / (256 - timernow) :
					78125 / (1024 - timernow) ;
				break;
			case 3:				/* 相対、@t */
				timernow += tmp;
				temponow = !timer?
					78125 / 16 / (256 - timernow) :
					78125 / (1024 - timernow) ;
				break;
		}

		if (tempo[j].attribute < 2) {		/* tで設定した時 */
			i = (roundMode)?
				(DWORD)(600000000L/temponow+5)/10 :		/* 小数点以下四捨五入 */
				(DWORD)(60000000L/temponow);			/* 小数点以下切り捨て */
		} else {				/* @tで設定した時 */
			i  = (timer==0)?
				60000000 / 78125 * 16 * (256 - timernow) :
				60000000 / ( 78125 / (1024 - timernow) );
		}

		MTrkTrk += PutVarLen(MTrkTrk,delta);	/* テンポ書き込み */
		*MTrkTrk++ = 0xFF;
		*MTrkTrk++ = 0x51;
		*MTrkTrk++ = 0x03;
		*MTrkTrk++ = (i & 0xFF0000)/0x10000;
		*MTrkTrk++ = (i & 0x00FF00)/0x100;
		*MTrkTrk++ = (i & 0x0000FF);

					/* 以下、次のイベント(＝先送りした元イベント)のδタイム変更 */
		deltabytes = GetVarLen(MTrkTrk,&newdelta);
		newdelta   = newdelta - delta;
		if (newdelta < 0) {	/* 最後尾に挿入時 */
			newdelta = 0;
		}
		newdeltabytes = get_VarLen_bytes(newdelta);
		if (deltabytes != newdeltabytes) {
			memmove(MTrkTrk + newdeltabytes,
				MTrkTrk + deltabytes,
				totalbytes + 8 - (MTrkTrk + deltabytes - &MTrk[0]));
		}
		PutVarLen(MTrkTrk,newdelta);

		FF2Fpos    += newdeltabytes - deltabytes;	/* 最終位置の修正後半 */
		totalbytes += newdeltabytes - deltabytes;	/* ヘッダのバイト数を修正 */
		/* *(int *)(MTrk + 4) = totalbytes; */
		putDwordAlign(MTrk + 4,totalbytes);
	}

#ifndef __GCC__
	efree(abstimesort, "abstimesort");
#endif

#ifdef DEBUG
	j = 0;
	for (i = 4; i <= 7; i++) {
		j <<= 8;
		j += MTrk[i];
	}
	fp=fopen("tempo","wb");		/* デバッグ用 */
	fwrite(&MTrk[0],sizeof(UBYTE),j+8,fp);
	fclose(fp);
#endif
}


/* ==========================================
     running statusを使ってないtrkに対して、
     delta timeの次のアドレスを入力すると、
     各イベントの情報を読み飛ばし、
     読み飛ばしたバイト数を返す。
   ========================================== */

DWORD skipevent(UBYTE *MTrk)
{
	int i = *MTrk;
	DWORD bytes = 0;

	switch (i & 0xF0) {
		case 0xC0:
		case 0xD0:
			fprintf( stderr,"CD?(%.2x): ???\n",i );
			bytes = 2;
			break;
		case 0xF0:	/* exclusive || meta event
				 * ここにしか来ない筈だが... */
			if (i == 0xFF) {	/* meta event */
				bytes = *(MTrk + 2) + 3;
				if ( *(MTrk+1) == 0x2F ) {	/* trk end */
					bytes = -bytes;
				}
			} else if (i == 0xF0) {
				bytes = *(MTrk + 1) + 2;
			} else {
				fprintf( stderr,"F?(%.2x): error!\n",i );
			}
			break;
		default:
			bytes = 3;
			fprintf( stderr,"?(%.2x): ???\n",i );
			break;
	}

	return bytes;
}
