/****************************<--OGI-->*************************************/
/*                                                                        */
/*             Center for Spoken Language Understanding                   */
/*        Oregon Graduate Institute of Science & Technology               */
/*                         Portland, OR USA                               */
/*                        Copyright (c) 2000                              */
/*                                                                        */
/*      This module is not part of the CSTR/University of Edinburgh       */
/*               release of the Festival TTS system.                      */
/*                                                                        */
/*  In addition to any conditions disclaimers below, please see the file  */
/*  "license_ogi_tts.txt" distributed with this software for information  */
/*  on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES.  */
/*                                                                        */
/****************************<--OGI-->*************************************/
#include <iostream.h>
#include <stdlib.h>
#include <stdio.h>
#include "festival.h"
#include "OGIdbase.h"
#include "OGI_file.h"

// model-specific functions
#include "OGIresLPC_db.h" // resLPC

#ifdef INCLUDE_OGISINE
#include "OGIsine_db.h"   // sinusoidal
#endif

static unsigned int OGI_DBASE_MAGIC=0xf149474e; // magic number for file read

static FILE *open_groupfile_read(EST_String &fname);
static FILE *open_groupfile_write(EST_String &fname);
static OGIbool check_groupfile_swap(FILE *gfd);
static OGIaccess_t get_access_mode(LISP params);
static OGIdata_t get_data_type(LISP params);
static void bounds_check_unit(int i, int N);

// constructor
OGIdbase::OGIdbase() : unamehash(2000){
  
}

// destructor
OGIdbase::~OGIdbase(){ 
  if (isGrouped && (gfd != NULL)){
    cout << "closed groupfile\n"; 
    fclose(gfd);
  }
}
  
//
// return a LISP representation of info about this unit
//
LISP OGIdbase::lispify_unit_info(int i){
  LISP segs = NIL;
  int n;

  //  bounds_check_unit(i, unitdic.Nunits());

  // Segs: Each member is of the form expected by utterance.cc 
  //  to create new items with item.append_daughter:
  //     ("uu" ((featname1 val1) (featname2 val2) ... ))
  for (n=0; n<unitdic.Nseg(i); n++){
    segs = append(segs, 
           cons(
	   cons(strintern(unitdic.seg_name(i,n)),
	   cons(append(
	     cons(make_param_float("dur",unitdic.seg_dur(i,n)/Fs),NIL),
                 // add new feats here
	     NIL),NIL)),NIL));  
    // ** to add new features -- see NOTES (2) at bottom
  }

  return
    cons(make_param_str("name",unitdic.name(i)),
    cons(make_param_int("db_indx",i),
    cons(make_param_int("Lindx",unitdic.Lindx(i)),
    cons(make_param_int("Rindx",unitdic.Rindx(i)),
    cons(make_param_int("Lbnd",unitdic.Lbnd(i)),
    cons(make_param_int("Rbnd",unitdic.Rbnd(i)),
    cons(make_param_lisp("segs",segs), 
    NIL)))))));
}
//
// return first indx with name name, or -1 if can't find it
// use a hash table for fast lookup of unit indices 
//
void OGIdbase::build_hash(void){
  for(int i=0; i<unitdic.Nunits(); i++) {
    unamehash.add_item(unitdic.name(i),i);
  }
}


//
// return first indx with name name, or -1 if can't find it
//
int OGIdbase::name_lookup_hash(const EST_String &unitname){
  int found=0;
  int r;
  
  r = unamehash.val(unitname, found);
  if (found)
    return r;
  else
    return -1;
}
int OGIdbase::name_lookup(const EST_String &unitname){
  //   old, slower(?) way
  for(int i=0; i<unitdic.Nunits(); i++) {
    if(unitdic.name(i) == unitname) {
      return i;
    }
  }
  return -1;
}

//
// return data type of loaded db
//
LISP OGIdbase::lispify_data_type(void){
  if (udata_type == d_resLPC){
    return strintern(EST_String("resLPC"));  
  }
  else if (udata_type == d_sine){
    return strintern(EST_String("sine"));  
  }
  else {
    return strintern(EST_String("none"));  
  }
}

void OGIdbase::parse_lisp_parameters(LISP params, OGIbool isGrp){
  EST_String sval;

  // used for either grp or ungrp
  name = wstrdup(get_param_str("dbname",params,"db"));
  access_mode = get_access_mode(params);
  dbglobal.override_fval("preemph", get_param_float("preemph",params,0.96));
  dbglobal.override_val("phoneset", wstrdup(get_param_str("phoneset",params,"none")));

  if (!isGrp){  // for ungrouped only (necessary ones will all be loaded already in grp case)
    fileinfo.override_val("unitdic_file",wstrdup(get_param_str("unitdic_file",params,"")));
    fileinfo.override_val("gain_file", wstrdup(get_param_str("gain_file",params,"none")));
    fileinfo.override_val("base_dir", wstrdup(get_param_str("base_dir",params,"./")));  
    
    // generic one 
    fileinfo.override_val("data_dir", wstrdup(get_param_str("data_dir",params,"data/")));
    fileinfo.override_val("data_ext", wstrdup(get_param_str("data_ext",params,".out")));

    // specific case for resLPC
    fileinfo.override_val("lpc_dir", wstrdup(get_param_str("lpc_dir",params,"lpc/")));
    fileinfo.override_val("lpc_ext", wstrdup(get_param_str("lpc_ext",params,".lpc")));
    fileinfo.override_val("exc_dir", wstrdup(get_param_str("exc_dir",params,"lpc/")));
    fileinfo.override_val("exc_ext", wstrdup(get_param_str("exc_ext",params,".res")));
    fileinfo.override_val("pm_dir", wstrdup(get_param_str("pm_dir",params,"pm/")));
    fileinfo.override_val("pm_ext", wstrdup(get_param_str("pm_ext",params,".pmv")));
    dbglobal.override_val("phoneset", wstrdup(get_param_str("phoneset",params,"none")));
    dbglobal.override_ival("lpc_order", get_param_int("lpc_order",params,0));
    dbglobal.override_fval("sig_band", get_param_float("sig_band",params,0)); // xtra samps saved around unit (in seconds!)
    Fs = get_param_float("samp_freq",params,16000.0); // in Hz
    isCompressed = (OGIbool) atobool(wstrdup(get_param_str("isCompressed",params,"Y"))); // compress when saved
    udata_type = get_data_type(params);  // resLPC, LPC, sine

  }
}

//////////////////////////////////////////////
////// ungrouped data access /////////////////
//////////////////////////////////////////////

void OGIdbase::load_ungrouped(LISP params){
  isGrouped = FALSE;

  // parse params passed in from scm by OGIdb.init
  parse_lisp_parameters(params,isGrouped);

  // Load the unit info from the unitdic file
  EST_String unitdic_file = fileinfo.sval("unitdic_file");
  unitdic.load_file(unitdic_file, Fs); 

  // build hash access table for unit names
  build_hash();

  // -create model-specific data obj
  // -load the data 
  if (udata_type == d_resLPC){ // resLPC
    OGIresLPC_db *u = new OGIresLPC_db;
    u->load_ungrp_udata(*this); 
    udata = u;
  }
  else if (udata_type == d_LPC){   // pulse-excit LPC coming soon
    EST_error("pulse-excit LPC mode not done yet\n");
  }
  else if (udata_type == d_sine){ // sinusoidal coming soon
#ifdef INCLUDE_OGISINE
    OGIsine_db *u = new OGIsine_db;
    u->load_ungrp_udata(*this); 
    udata = u;
#else
    EST_error("sine mode not included\n");    
#endif
  }
  else {
    EST_error("OGIdbase.cc: unknown udata type\n");
  }  
}


//////////////////////////////////////////////
////// grouped data access ///////////////////
//////////////////////////////////////////////
void OGIdbase::load_grouped(EST_String &groupfile, LISP usr_params){
  isGrouped = TRUE;

  // open grp file, read magic #
  gfd = open_groupfile_read(groupfile);
  swap = check_groupfile_swap(gfd);

  // load header info in grp file
  load_grp_params();

  // passed in by user
  parse_lisp_parameters(usr_params,isGrouped);    

  // load unitdic info
  unitdic.load_grp(gfd, swap);

  // build hash access table for unit names
  build_hash();

  // -read default params as specified in the groupfile
  // -overwrite with user-specified params
  // -create model-specific data obj
  // -load the data
 if (udata_type == d_resLPC){ // resLPC
    OGIresLPC_db *u = new OGIresLPC_db;
    u->load_grp_udata(*this); 
    udata = u;
  }
  else if (udata_type == d_LPC){   // pulse-excit LPC coming soon
    EST_error("pulse-excit LPC mode not done yet\n");
  }
  else if (udata_type == d_sine){ // sinusoidal coming soon
    EST_error("sine mode not done yet\n");
  }
  else {
    EST_error("OGIdbase.cc: unknown udata type\n");
  }  
}

void OGIdbase::save_grouped(EST_String &groupfile){
  
  isGrouped = FALSE;
  unsigned int magic = OGI_DBASE_MAGIC;

  // open file, write magic num
  gfd = open_groupfile_write(groupfile);
  fwrite(&magic,sizeof(int), 1, gfd);

  // write header, dbase params
  save_grp_params();

  // write unitdic info
  unitdic.save_grp(gfd);
  
  if (udata_type == d_resLPC){ // resLPC
    ((OGIresLPC_db *) udata)->save_grp_udata(*this); 
  }
  else if (udata_type == d_LPC){   // pulse-excit LPC coming soon
    EST_error("pulse-excit LPC mode not done yet\n");
  }
  else if (udata_type == d_sine){ // sinusoidal coming soon
    EST_error("sine grouped mode not done yet\n");
  }
  else {
    EST_error("OGIdbase.cc: unknown udata type\n");
  }  

  fclose(gfd);
}

void OGIdbase::load_grp_params(void){
  OGIfread(&isCompressed, sizeof(OGIbool), 1, gfd, swap);
  OGIfread(&Fs, sizeof(float), 1, gfd, swap);
  OGIfread(dbglobal, gfd);

  int x;
  OGIfread(&x, sizeof(int), 1, gfd, swap);
  udata_type = (enum OGIdata_t) x;


}

void OGIdbase::save_grp_params(void){
  fwrite(&isCompressed, sizeof(OGIbool), 1, gfd);
  fwrite(&Fs, sizeof(float), 1, gfd);
  OGIfwrite(dbglobal, gfd);
  int x;
  x = (int) udata_type;
  fwrite(&x, sizeof(int), 1, gfd);
}


static FILE *open_groupfile_read(EST_String &fname){
  //      - open file, 
  //      - get magic number
  //      - return fd
  FILE *gfd;

  if ((gfd=fopen(fname,"rb")) == NULL){
    EST_error("OGIdbase.cc: cannot open group file " + fname + " for reading\n");
  }
  return gfd;
}
static FILE *open_groupfile_write(EST_String &fname){
  //      - open file, 
  //      - get magic number
  //      - return fd
  FILE *gfd;

  if ((gfd=fopen(fname,"wb")) == NULL){
    EST_error("OGIdbase.cc: cannot open group file " + fname + " for writing\n");
  }
  
  return gfd;
}

static OGIbool check_groupfile_swap(FILE *gfd){
  unsigned int magic;

  fread(&magic,sizeof(int), 1, gfd);
  if (magic == SWAPINT(OGI_DBASE_MAGIC))
    return TRUE;
  else if (magic == OGI_DBASE_MAGIC)
    return FALSE;
  else {
    EST_error("OGIdbase.cc:   not a valid OGI format group file\n");
  }
  return FALSE;
}

//////////////////////////////////////////////
////// little utilities  /////////////////////
//////////////////////////////////////////////

static OGIaccess_t get_access_mode(LISP params){
  EST_String sval = wstrdup(get_param_str("access_mode",params,"unset"));
  if (sval == "direct")           return a_direct;
  else if (sval == "dynamic")     return a_dynamic;
  else if (sval == "ondemand")    return a_ondemand;
  else {
    cerr << "OGIdbase.cc: Unknown access mode: \"" << sval << "\" -- using direct access.\n";
    return a_direct; 
  }
}
static OGIdata_t get_data_type(LISP params){
  EST_String sval = wstrdup(get_param_str("data_type",params,"unset"));
  if (sval == "resLPC")           return d_resLPC;
  else if (sval == "LPC")         return d_LPC;
  else if (sval == "sine")        return d_sine;
  else {
    EST_error("OGIdbase.cc: Unknown data type \"" + sval + "\"\n");
    return d_unset; // dummy
  }
}

static void bounds_check_unit(int i, int N){
  if (i >= N){
    cerr << "OGIdbase ERROR: request for unit " << i << " in " << N << " unit dbase.\n";
    EST_error("exiting\n");
  }
}



#if 0
/*
NOTES:

(1) for lispify unit info:  

    // for future reference: example returning 3 features in segs
    segs = append(segs, 
           cons(
	   cons(strintern(unitdic.seg_name(i,n)),
	   cons(append(
	     cons(make_param_float("dur",unitdic.seg_dur(i,n)/Fs),NIL),
	     append(
	     cons(make_param_float("xxx",unitdic.seg_dur(i,n)/Fs),NIL),
	     cons(make_param_float("yyy",unitdic.seg_dur(i,n)/Fs),NIL)
	     )),NIL)),NIL));  

*/
#endif


