/*

Name:
MLOADER.C

Description:
These routines are used to access the available module loaders

Portability:
All systems - all compilers

*/

#include <string.h>
#include "mikmod.h"
#include "Dual.h"

CHAR  *MLERR_LOADING_PATTERN    = "Failure loading module pattern.";
CHAR  *MLERR_LOADING_TRACK      = "Failure loading module track.";
CHAR  *MLERR_LOADING_HEADER     = "Failure loading module.";
CHAR  *MLERR_NOT_A_MODULE       = "Unknown module format.";
CHAR  *MLERR_LOADING_SAMPLEINFO = "Failure loading sampleinfo.";

FILE *modfp;
UNIMOD of;

static MLOADER *firstloader = NULL;

UWORD finetune[16] =
{   8363,   8413,   8463,   8529,   8581,   8651,   8723,   8757,
    7895,   7941,   7985,   8046,   8107,   8169,   8232,   8280
};

void ML_InfoLoader(void)
{
    int      t;
    MLOADER *l;

    // list all registered devicedrivers:

    for(t=1,l=firstloader; l!=NULL; l=l->next, t++)
        printf("%d. %s\n",t,l->version);
}


void ML_RegisterLoader(MLOADER *ldr)
{
    MLOADER *cruise = firstloader;

    if(cruise!=NULL)
    {   while(cruise->next!=NULL)  cruise = cruise->next;
        cruise->next = ldr;
    } else
        firstloader = ldr; 
}

void ML_UnRegisterLoader(MLOADER *ldr)
{
    MLOADER *cruise = firstloader;

	while(cruise->next!=ldr)
	{
		cruise = cruise->next;
		if(cruise==NULL) return;
	}
	
	if(cruise->next)
	{
		cruise->next=cruise->next->next;
	}
}

BOOL ReadComment(UWORD len)
{
    //int t;

    if(len)
    {   if(!(of.comment=(CHAR *)_mm_malloc(len+1))) return 0;
        fread(of.comment,len,1,modfp);
        of.comment[len] = 0;
    }
    return 1;
}


BOOL AllocPatterns(void)
{
    int s,t,tracks = 0;

    // Allocate track sequencing array

    if(!(of.patterns = (UWORD *)_mm_calloc((ULONG)of.numpat*of.numchn,sizeof(UWORD)))) return 0;
    if(!(of.pattrows = (UWORD *)_mm_calloc(of.numpat,sizeof(UWORD)))) return 0;

    for(t=0; t<of.numpat; t++)
    {   of.pattrows[t] = 64;
        for(s=0; s<of.numchn; s++)
            of.patterns[(t*of.numchn)+s] = tracks++;
    }

    return 1;
}


BOOL AllocTracks(void)
{
    if(!(of.tracks=(UBYTE **)_mm_calloc(of.numtrk,sizeof(UBYTE *)))) return 0;
    return 1;
}


BOOL AllocInstruments(void)
{
    int t,n;

    if((of.instruments=(INSTRUMENT *)_mm_calloc(of.numins,sizeof(INSTRUMENT)))==NULL) return 0;

    for(t=0; t<of.numins; t++)
    {  for(n=0; n<120; n++)     // Init note / sample lookup table
       {  of.instruments[t].samplenote[n]   = n;
          of.instruments[t].samplenumber[n] = t;
       }   
       of.instruments[t].globvol = 64;
    }
    return 1;
}


BOOL AllocSamples(void)
{
    UWORD u;

    if((of.samples=(SAMPLE *)_mm_calloc(of.numsmp,sizeof(SAMPLE)))==NULL) return 0;

    for(u=0; u<of.numsmp; u++)
    {   of.samples[u].panning = 128;
        of.samples[u].handle  = -1;
        of.samples[u].globvol = 64;
    }
    return 1;
}


BOOL ML_LoadSamples(void)
{
    SAMPLE *s;
    int    u;

    for(u=of.numsmp, s=of.samples; u; u--, s++)
        if(s->length) SL_RegisterSample(s,modfp);

    return 1;
}


CHAR *DupStr(CHAR *s, UWORD len)
//  Creates a CSTR out of a character buffer of 'len' bytes, but strips
//  any terminating non-printing characters like 0, spaces etc.
{
    UWORD t;
    CHAR  *d = NULL;

    // Scan for first printing char in buffer [includes high ascii up to 254]
    while(len)
    {   if(s[len-1] > 0x20) break;
        len--;
    }

    // When the buffer wasn't completely empty, allocate
    // a cstring and copy the buffer into that string, except
    // for any control-chars

    if((d=(CHAR *)_mm_malloc(len+1)) != NULL)
    {   for(t=0; t<len; t++) d[t] = (s[t]<32) ? ' ' : s[t];
        d[t] = 0;
    }

    return d;
}


static void ML_XFreeSample(SAMPLE *s)
{
   if(s->handle>=0)
      MD_SampleUnLoad(s->handle);
   if(s->samplename!=NULL) free(s->samplename);
}


static void ML_XFreeInstrument(INSTRUMENT *i)
{
    if(i->insname!=NULL) free(i->insname);
}


static void ML_FreeEx(UNIMOD *mf)
{
    UWORD t;

    if(mf->modtype!=NULL) free(mf->modtype);
    if(mf->patterns!=NULL) free(mf->patterns);
    if(mf->pattrows!=NULL) free(mf->pattrows);

    if(mf->tracks!=NULL)
    {   for(t=0; t<mf->numtrk; t++)
            if(mf->tracks[t]!=NULL) free(mf->tracks[t]);
        free(mf->tracks);
    }

    if(mf->instruments!=NULL)
    {   for(t=0; t<mf->numins; t++)
            ML_XFreeInstrument(&mf->instruments[t]);
        free(mf->instruments);
    }

    if(mf->samples!=NULL)
    {   for(t=0; t<mf->numsmp; t++)
          if(mf->samples[t].length) ML_XFreeSample(&mf->samples[t]);
        free(mf->samples);
    }
    
    if(mf->songname!=NULL) free(mf->songname);
    if(mf->comment!=NULL)  free(mf->comment);
}


static UNIMOD *ML_AllocUniMod(void)
{
   UNIMOD *mf;
   
   if((mf=(UNIMOD *)_mm_calloc(1,sizeof(UNIMOD))) == NULL) return NULL;
   return mf;
}


/******************************************

    Next are the user-callable functions

******************************************/


void ML_Free(UNIMOD *mf)
{
   if(mf!=NULL)
   {   MP_Exit(mf);
       ML_FreeEx(mf);
   }
}

extern Object *dual_object;
MLOADER *glb_loader;

UNIMOD *ML_LoadFP(FILE *fp)
{
    int      t;
    MLOADER *l;
    BOOL     ok;
    UNIMOD  *mf;

    modfp = fp;
    _mm_error = NULL;

    // Try to find a loader that recognizes the module

    for(l=firstloader; l!=NULL; l=l->next)
    {   _mm_rewind(modfp);
        if(l->Test()) break;
    }

    glb_loader=l;

    if(l==NULL)
    {   _mm_error = MLERR_NOT_A_MODULE;
        return NULL;
    }

    // init unitrk routines
    if(!UniInit()) 
    {
    	return NULL;
    }

	if(dual_object)dual_object->DoMethod(D_NEW_MESSAGE,"Loading tracks...");
	
    // load the song using the song's loader variable
    memset(&of,0,sizeof(UNIMOD));
    of.volume = 128;

    // init panning array
    for(t=0; t<64; t++) of.panning[t] = ((t+1)&2) ? 255 : 0;
    for(t=0; t<64; t++) of.chanvol[t] = 64;

    // init module loader and load the header / patterns
    if(l->Init())
    {   _mm_rewind(modfp);
        ok = l->Load();
    }

    // free loader and unitrk allocations
    l->Cleanup();
    UniCleanup();

    if(!ok)
    {
    	return NULL;
    }

	if(dual_object)dual_object->DoMethod(D_NEW_MESSAGE,"Loading Samples...");

    if(!ML_LoadSamples())
    {   ML_FreeEx(&of);
        return NULL;
    }


    if((mf=ML_AllocUniMod()) == NULL)
    {   ML_FreeEx(&of);
        return NULL;
    }

    // Copy the static UNIMOD contents into the dynamic UNIMOD struct.
    memcpy(mf,&of,sizeof(UNIMOD));

    return mf;
}


UNIMOD *ML_LoadFN(CHAR *filename, int maxchan)
// Open a module via it's filename.  The loader will initialize the specified
// song-player 'player'.
{
    FILE *fp;
    UNIMOD *mf;
    
    if((fp=_mm_fopen(filename,"r"))==NULL) return NULL;
    if(mf=ML_LoadFP(fp))
    {
	  if(strncmp(mf->modtype,"ImpulseT",8))maxchan=mf->numchn;
      if(!(!SL_LoadSamples() && !MD_SetNumChannels(maxchan,0) && !MP_Init(mf)))
   	  {   ML_Free(mf);
          mf = NULL;
      }
	}
	
    fclose(fp);
    return mf;
}

