/*
  ZynAddSubFX - a software synthesizer
 
  Bank.h - Instrument Bank 
  Copyright (C) 2002-2005 Nasca Octavian Paul
  Author: Nasca Octavian Paul

  This program is free software; you can redistribute it and/or modify
  it under the terms of version 2 of the GNU General Public License 
  as published by the Free Software Foundation.

  This program 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 (version 2) for more details.

  You should have received a copy of the GNU General Public License (version 2)
  along with this program; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

*/

#include "Bank.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>

#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include "Config.h"

#define INSTRUMENT_EXTENSION ".xiz"

//if this file exists into a directory, this make the directory to be considered as a bank, even if it not contains a instrument file
#define FORCE_BANK_DIR_FILE ".bankdir"

Bank::Bank(){


    ZERO(defaultinsname,PART_MAX_NAME_LEN);
    snprintf(defaultinsname,PART_MAX_NAME_LEN,"%s"," ");
        
    for (int i=0;i<BANK_SIZE;i++){
	ins[i].used=false;
	ins[i].filename=NULL;
	ins[i].info.PADsynth_used=false;
    };
    dirname=NULL;
    clearbank();



    for (int i=0;i<MAX_NUM_BANKS;i++){
	banks[i].dir=NULL;
	banks[i].name=NULL;
    };
    
    bankfiletitle=dirname;

    loadbank(config.cfg.currentBankDir);

};

Bank::~Bank(){
    for (int i=0;i<MAX_NUM_BANKS;i++){
	if (banks[i].dir!=NULL) delete (banks[i].dir);
	if (banks[i].name!=NULL) delete (banks[i].name);
    };

    clearbank();
};

/*
 * Get the name of an instrument from the bank
 */
char *Bank::getname (unsigned int ninstrument){
    if (emptyslot(ninstrument)) return (defaultinsname);
    return (ins[ninstrument].name);
};

/*
 * Get the numbered name of an instrument from the bank
 */
char *Bank::getnamenumbered (unsigned int ninstrument){
    if (emptyslot(ninstrument)) return (defaultinsname);
    snprintf(tmpinsname[ninstrument],PART_MAX_NAME_LEN+15,"%d. %s",ninstrument+1,getname(ninstrument));
    return(tmpinsname[ninstrument]);
};

/*
 * Changes the name of an instrument (and the filename)
 */
void Bank::setname(unsigned int ninstrument,const char *newname,int newslot){
    if (emptyslot(ninstrument)) return;
    
    char newfilename[1000+1],tmpfilename[100+1];
    
    ZERO(newfilename,1001);
    ZERO(tmpfilename,101);
    if (newslot>=0) snprintf(tmpfilename,100,"%4d-%s",newslot+1,newname);
	else snprintf(tmpfilename,100,"%4d-%s",ninstrument+1,newname);
    
    //add the zeroes at the start of filename
    for (int i=0;i<4;i++) if (tmpfilename[i]==' ') tmpfilename[i]='0';

    //make the filenames legal
    for (int i=0;i<(int) strlen(tmpfilename);i++) {
	char c=tmpfilename[i];
	if ((c>='0')&&(c<='9')) continue;
	if ((c>='A')&&(c<='Z')) continue;
	if ((c>='a')&&(c<='z')) continue;
	if ((c=='-')||(c==' ')) continue;
	
	tmpfilename[i]='_';
    };

    snprintf(newfilename,1000,"%s/%s.xiz",dirname,tmpfilename);

//    printf("rename %s -> %s\n",ins[ninstrument].filename,newfilename);//////////////

    rename(ins[ninstrument].filename,newfilename);
    if (ins[ninstrument].filename) delete(ins[ninstrument].filename);
    ins[ninstrument].filename=new char[strlen(newfilename)+5];
    snprintf(ins[ninstrument].filename,strlen(newfilename)+1,"%s",newfilename);
    snprintf(ins[ninstrument].name,PART_MAX_NAME_LEN,"%s",&tmpfilename[5]);
    
};

/*
 * Check if there is no instrument on a slot from the bank
 */
int Bank::emptyslot(unsigned int ninstrument){
    if (ninstrument>=BANK_SIZE) return (1);
    if (ins[ninstrument].filename==NULL) return(1);

    if (ins[ninstrument].used) return (0);
	else return(1);
};

/*
 * Removes the instrument from the bank
 */
void Bank::clearslot(unsigned int ninstrument){
    if (emptyslot(ninstrument)) return;
    
//    printf("remove  %s  \n",ins[ninstrument].filename);////////////////////////
    
    
    remove(ins[ninstrument].filename);
    deletefrombank(ninstrument);
};

/*
 * Save the instrument to a slot 
 */
void Bank::savetoslot(unsigned int ninstrument,Part *part){
    clearslot(ninstrument);
    
    const int maxfilename=200;
    char tmpfilename[maxfilename+20];
    ZERO(tmpfilename,maxfilename+20);

    snprintf(tmpfilename,maxfilename,"%4d-%s",ninstrument+1,(char *)part->Pname);

    //add the zeroes at the start of filename
    for (int i=0;i<4;i++) if (tmpfilename[i]==' ') tmpfilename[i]='0';
    
    //make the filenames legal
    for (int i=0;i<(int)strlen(tmpfilename);i++) {
	char c=tmpfilename[i];
	if ((c>='0')&&(c<='9')) continue;
	if ((c>='A')&&(c<='Z')) continue;
	if ((c>='a')&&(c<='z')) continue;
	if ((c=='-')||(c==' ')) continue;
	
	tmpfilename[i]='_';
    };

    strncat(tmpfilename,".xiz",maxfilename+10);

    int fnsize=strlen(dirname)+strlen(tmpfilename)+10;
    char *filename=new char[fnsize+4];
    ZERO(filename,fnsize+2);

    snprintf(filename,fnsize,"%s/%s",dirname,tmpfilename);

    remove(filename);
    part->saveXML(filename);
    addtobank(ninstrument,tmpfilename,(char *) part->Pname);
    
    delete(filename);
};

/*
 * Loads the instrument from the bank
 */
void Bank::loadfromslot(unsigned int ninstrument,Part *part){
    if (emptyslot(ninstrument)) return;
    
    part->defaultsinstrument();

//    printf("load:  %s\n",ins[ninstrument].filename);
    
    part->loadXMLinstrument(ins[ninstrument].filename);
    
};


/*
 * Makes current a bank directory
 */
int Bank::loadbank(const char *bankdirname)
      {
      DIR *dir = opendir(bankdirname);
      clearbank();

      if (dir==NULL) 
            return -1;

      if (dirname!=NULL) 
            delete(dirname);
      dirname = new char[strlen(bankdirname)+1];
      snprintf(dirname, strlen(bankdirname)+1, "%s",bankdirname);
    
      bankfiletitle=dirname;

      // printf("loadbank %s/\n",bankdirname);
      struct dirent *fn;
        
    while ((fn=readdir(dir))){
	const char *filename= fn->d_name;
	
	//sa verific daca e si extensia dorita
	if (strstr(filename,INSTRUMENT_EXTENSION)==NULL) continue;
	
	//verify if the name is like this NNNN-name (where N is a digit)
	int no=0;
	unsigned int startname=0;
	
	for (unsigned int i=0;i<4;i++) {
	    if (strlen(filename)<=i) break;

	    if ((filename[i]>='0')&&(filename[i]<='9')) {
		no=no*10+(filename[i]-'0');
		startname++;
	    };
	};
	
	
	if ((startname+1)<strlen(filename)) startname++;//to take out the "-"
	
	char name[PART_MAX_NAME_LEN+1]; 
	ZERO(name,PART_MAX_NAME_LEN+1);
	snprintf(name,PART_MAX_NAME_LEN,"%s",filename);
	
	//remove the file extension
	for (int i=strlen(name)-1;i>=2;i--){
	    if (name[i]=='.') {
		name[i]='\0';
		break;
	    };
	};
	
	if (no!=0){//the instrument position in the bank is found
	    addtobank(no-1,filename,&name[startname]);
	} else {
	    addtobank(-1,filename,name);
	};
	
    };
    
    
    closedir(dir);

    if (dirname!=NULL) {
	sprintf(config.cfg.currentBankDir,"%s",dirname);
    };

    return(0);
};

/*
 * Makes a new bank, put it on a file and makes it current bank
 */
int Bank::newbank(const char *newbankdirname){
    int result;
    char tmpfilename[MAX_STRING_SIZE];
    char bankdir[MAX_STRING_SIZE];
    snprintf(bankdir,MAX_STRING_SIZE,"%s",config.cfg.bankRootDirList[0]);

    if (((bankdir[strlen(bankdir)-1])!='/')&&((bankdir[strlen(bankdir)-1])!='\\')){
	strncat(bankdir,"/",MAX_STRING_SIZE);
    };
    strncat(bankdir,newbankdirname,MAX_STRING_SIZE);
#ifdef OS_WINDOWS
    result=mkdir(bankdir);
#else
    result=mkdir(bankdir,S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif
    if (result<0) return(-1);

    snprintf(tmpfilename,MAX_STRING_SIZE,"%s/%s",bankdir,FORCE_BANK_DIR_FILE);
//    printf("%s\n",tmpfilename);
    FILE *tmpfile=fopen(tmpfilename,"w+");
    fclose(tmpfile);
    
    return(loadbank(bankdir));
};

/*
 * Check if the bank is locked (i.e. the file opened was readonly)
 */
int Bank::locked(){
    return(dirname==NULL);
};

/*
 * Swaps a slot with another
 */
void Bank::swapslot(unsigned int n1, unsigned int n2){
    if ((n1==n2)||(locked())) return;
    if (emptyslot(n1)&&(emptyslot(n2))) return;
    if (emptyslot(n1)){//change n1 to n2 in order to make
	int tmp=n2;n2=n1;n1=tmp;
    };
    
    if (emptyslot(n2)){//this is just a movement from slot1 to slot2
	setname(n1,getname(n1),n2);
	ins[n2]=ins[n1];
	ins[n1].used=false;
	ins[n1].name[0]='\0';
	ins[n1].filename=NULL;
	ins[n1].info.PADsynth_used=0;
    } else {//if both slots are used
	if (strcmp(ins[n1].name,ins[n2].name)==0){//change the name of the second instrument if the name are equal
	    strncat(ins[n2].name,"2",PART_MAX_NAME_LEN);
	};
	setname(n1,getname(n1),n2);
	setname(n2,getname(n2),n1);
	ins_t tmp;
	tmp.used=true;
	strcpy(tmp.name,ins[n2].name);
	char *tmpfilename=ins[n2].filename;
	bool padsynth_used=ins[n2].info.PADsynth_used;
	
	ins[n2]=ins[n1];
	strcpy(ins[n1].name,tmp.name);
	ins[n1].filename=tmpfilename;
	ins[n1].info.PADsynth_used=padsynth_used;
    };
    
};


//a helper function that compares 2 banks[] arrays
int Bank_compar(const void *a,const void *b){
    struct Bank::bankstruct *bank1= (Bank::bankstruct *)a;
    struct Bank::bankstruct *bank2= (Bank::bankstruct *)b;
    if (((bank1->name)==NULL)||((bank2->name)==NULL)) return(0);

    int result=strcasecmp(bank1->name,bank2->name);
    return(result<0);
};


/*
 * Re-scan for directories containing instrument banks
 */

void Bank::rescanforbanks(){
    for (int i=0;i<MAX_NUM_BANKS;i++){
	if (banks[i].dir!=NULL) delete (banks[i].dir);
	if (banks[i].name!=NULL) delete (banks[i].name);
	banks[i].dir=NULL;
	banks[i].name=NULL;
    };

    for (int i=0;i<MAX_BANK_ROOT_DIRS;i++) if (config.cfg.bankRootDirList[i]!=NULL) scanrootdir(config.cfg.bankRootDirList[i]);

    //sort the banks
    for (int j=0;j<MAX_NUM_BANKS-1;j++){
	for (int i=j+1;i<MAX_NUM_BANKS;i++){
	    if (Bank_compar(&banks[i],&banks[j])) {
		char *tmpname=banks[i].name;
		char *tmpdir=banks[i].dir;

		banks[i].name=banks[j].name;
		banks[i].dir=banks[j].dir;

		banks[j].name=tmpname;
		banks[j].dir=tmpdir;
		
	    };
	};
    };
    
    //remove duplicate bank names
    int dupl=0;
    for (int j=0;j<MAX_NUM_BANKS-1;j++){
	for (int i=j+1;i<MAX_NUM_BANKS;i++){
	    if ((banks[i].name==NULL)||(banks[j].name==NULL)) continue;
	    if (strcmp(banks[i].name,banks[j].name)==0) {//add a [1] to the first bankname and [n] to others
	        char *tmpname=banks[i].name;
	        banks[i].name=new char[strlen(tmpname)+100];
	        sprintf(banks[i].name,"%s[%d]",tmpname,dupl+2);
	        delete(tmpname);
		
		if (dupl==0){
	    	    char *tmpname=banks[j].name;
	            banks[j].name=new char[strlen(tmpname)+100];
		    sprintf(banks[j].name,"%s[1]",tmpname);
	    	    delete(tmpname);
		};
		
	        dupl++;
	    } else dupl=0;
	};
    };
    
}; 



// private stuff

void Bank::scanrootdir(char *rootdir){
//    printf("Scanning root dir:%s\n",rootdir);
    DIR *dir=opendir(rootdir);
    if (dir==NULL) return;

    const int maxdirsize=1000;
    struct {
	char dir[maxdirsize];
	char name[maxdirsize];	
    }bank;

    char *separator="/";
    if (strlen(rootdir)) {
	char tmp=rootdir[strlen(rootdir)-1];
	if ((tmp=='/') || (tmp=='\\')) separator="";
    };
    
    struct dirent *fn;
    while ((fn=readdir(dir))){
	const char *dirname=fn->d_name;
	if (dirname[0]=='.') continue;
	
	snprintf(bank.dir,maxdirsize,"%s%s%s/",rootdir,separator,dirname);
	snprintf(bank.name,maxdirsize,"%s",dirname);
	//find out if the directory contains at least 1 instrument
	bool isbank=false;
	
	DIR *d=opendir(bank.dir);
	if (d==NULL) continue;
	
	struct dirent *fname;

	while((fname=readdir(d))){
	    if ((strstr(fname->d_name,INSTRUMENT_EXTENSION)!=NULL)||
	       (strstr(fname->d_name,FORCE_BANK_DIR_FILE)!=NULL)){
		isbank=true;
		break;//aici as putea pune in loc de break un update la un counter care imi arata nr. de instrumente din bank
	    };
	};
	
	closedir(d);	
    
	if (isbank) {
	    int pos=-1;
	    for (int i=1;i<MAX_NUM_BANKS;i++){	//banks[0] e liber intotdeauna
		if (banks[i].name==NULL) {
		    pos=i;
		    break;
		};
	    };
	    
	    if (pos>=0) {
		banks[pos].name=new char[maxdirsize];
		banks[pos].dir=new char[maxdirsize];
    		snprintf(banks[pos].name,maxdirsize,"%s",bank.name);
    		snprintf(banks[pos].dir,maxdirsize,"%s",bank.dir);
	    };
	    
	};
	
    };

    closedir(dir);
    
};

void Bank::clearbank(){
    for (int i=0;i<BANK_SIZE;i++) deletefrombank(i);
    if (dirname!=NULL) delete(dirname);
    bankfiletitle=NULL;
    dirname=NULL;
};

int Bank::addtobank(int pos, const char *filename, const char* name){
    if ((pos>=0)&&(pos<BANK_SIZE)){
	if (ins[pos].used) pos=-1;//force it to find a new free position
    } else if (pos>=BANK_SIZE) pos=-1;
    
    
    if (pos<0) {//find a free position
	for (int i=BANK_SIZE-1;i>=0;i--)
	    if (!ins[i].used) {
		pos=i;
		break;
	    };
	
    };
    
    if (pos<0) return (-1);//the bank is full

   // printf("%s   %d\n",filename,pos);

    deletefrombank(pos);
    
    ins[pos].used=true;
    snprintf(ins[pos].name,PART_MAX_NAME_LEN,"%s",name);

    snprintf(tmpinsname[pos],PART_MAX_NAME_LEN+10," ");

    int len=strlen(filename)+1+strlen(dirname);
    ins[pos].filename=new char[len+2];
    ins[pos].filename[len+1]=0;
    snprintf(ins[pos].filename,len+1,"%s/%s",dirname,filename);

    //see if PADsynth is used
    if (config.cfg.CheckPADsynth){
	XMLwrapper *xml=new XMLwrapper();
	xml->checkfileinformation(ins[pos].filename);
    
	ins[pos].info.PADsynth_used=xml->information.PADsynth_used;
	delete(xml);
    } else ins[pos].info.PADsynth_used=false;
    
    return(0);
};

bool Bank::isPADsynth_used(unsigned int ninstrument){
    if (config.cfg.CheckPADsynth==0) return(0);
	else return(ins[ninstrument].info.PADsynth_used);
};


void Bank::deletefrombank(int pos){
    if ((pos<0)||(pos>=BANK_SIZE)) return;
    ins[pos].used=false;
    ZERO(ins[pos].name,PART_MAX_NAME_LEN+1);
    if (ins[pos].filename!=NULL) {
	delete (ins[pos].filename);
	ins[pos].filename=NULL;
    };
    
    ZERO(tmpinsname[pos],PART_MAX_NAME_LEN+20);
    
};