#include <string.h>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>
#include <fstream.h>
#include <time.h>
#pragma hdrstop

#include "cryosim.h"

/* CryoSimulation v1.01
 *
 * Written by:  Chris Studholme
 * Last Update: 28-Oct-1996
 * Copyright:   GPL (http://www.fsf.org/copyleft/gpl.html)
 * Web Site:    http://www.bus.ualberta.ca/cstudhol/msc/
 */

#define title		"CryoSim"
#define version		"v1.00"

#define fsPathName 	128
#define fsFileName 	32
#define statusLen	96
#define numLen		16

#define SimFileId	'Sm'

#define ABS(a)		((a)<0?(-(a)):(a))

#define ERROR(method,error) \
	fprintf(stderr,"ERROR in %s, %s\n",method,error)
#define WARNING(method,warning) \
	fprintf(stderr,"WARNING in %s, %s\n",method,warning)

//  Struct's

struct SimParms {
  REAL maxdt;		// maximum allowed dt (sec)
  REAL mindt;  		// minimum allowed dt (sec)
  REAL endTime;		// simulation run time (sec)
  REAL startTemp;	// starting temperature (K)
  REAL tempRate;	// temperature rate (K/sec)
  REAL endTemp;		// ending temperature (K)
  REAL reportTime;	// maximum time between reports (sec)
  int reportIter;	// maximum number of iterations between reports (int)
  REAL dendriteBreakdown;	// time of dendritic breakdown (sec)
};

// Global Variables

SimParms sp;

Compartment *root = NULL;	// root of simulation
Solution *solutionList = NULL;	// list of solutions needed by simulation
ofstream *fout = NULL;		// stream to results file

typedef REAL* pREAL;
int numREALs=0;
pREAL *REALlist;

typedef int32* pINT32;
int numINT32s=0;
pINT32 *INT32list;

FILE *status, *extraData;

//  Global Methods

void WriteVariablePointersToStream(const Solution*, opstream&) {
}

void AddVariablePointerToList(uint32 index, void *p) {
  if (index&0x80000000) {		// int32 list
    if ((index&0x7FFFFFFF)<numINT32s)
      INT32list[index&0x7FFFFFFF] = (int32*)p;
    return;
  }
  
  // else REAL list
  if (index<numREALs)
    REALlist[index] = (REAL*)p;
}


// Outputs the current time, and the values of all variables to
// the tab-delimited text file pointed to by fout.
void Report(int iter, REAL T) {
  printf("%g,%d,%g", t, iter, T);
  
  for(int i=0; i<numREALs; i++)
    if(REALlist[i])
      printf(",%g", *(REALlist[i]));
    else
      printf(",");
  
  for(int i=0; i<numINT32s; i++)
    if(INT32list[i])
      printf(",%d", *(INT32list[i]));
    else
      printf(",");
  
  printf("\n");
  
  if (extraData) {
    fprintf(extraData,"%.02f,%d,%.02f\n", t, iter, T);
    DCompartment *dc = dynamic_cast<DCompartment*>(root);
    const DCompartment *dc2 = 
      dynamic_cast<const DCompartment*>(root->GetCompartments());
    if (dc) dc->DisplayStatus(extraData);
    else if (dc2) dc2->DisplayStatus(extraData);
  }
}


/* loads the simulation from a file created using the SimMaker program.
 * The simulation consists of a Root object, which may contain many child
 * objects.  Note that loading the Root object automatically results in
 * the loading of all child objects (ie. the entire simulation).  After
 * the root object, there may or may not be a list of Solutions.  If
 * solutions are present, they are loaded.  These solutions can be used
 * by the simulation if, for example, a cell were being transferred
 * through a series of solutions and monitored for responses.
 */
int LoadSimulation(char *fName) {
  //	ifpstream is(fName);
  ipstream is(fName);
  if (!is.good()) 
    return 0;
  
  uint16 id, ver;
  id = is.readWord16();			// file type identifier
  ver = is.readWord16();		// file version
  if (id!=SimFileId)
    ERROR("SIMDOER::LoadSimulation", "invalid file id");
  if (ver!=0x102)
    ERROR("SIMDOER::LoadSimulation", "invalid file version");
  
  char name[128];
  is.readString(name, 128);		// simulation name
  
  sp.maxdt = is.readDouble();
  sp.mindt = is.readDouble();
  sp.endTime = is.readDouble();
  sp.startTemp = is.readDouble();
  sp.tempRate = is.readDouble();
  sp.endTemp = is.readDouble();
  sp.reportTime = is.readDouble();
  sp.reportIter = is.readWord32();
  sp.dendriteBreakdown = is.readDouble();

  numREALs = is.readWord32();
  if (numREALs) {
    REALlist = new pREAL[numREALs];
    memset(REALlist, 0, sizeof(pREAL)*numREALs);
  }
  else 
    REALlist=0;
  
  numINT32s = is.readWord32();
  if (numINT32s) {
    INT32list = new pINT32[numINT32s];
    memset(INT32list, 0, sizeof(pINT32)*numINT32s);
  }
  else 
    INT32list=0;
  
  if (is.good())
    root = dynamic_cast<Compartment*>(is.readObject());
  if (!root) 
    return 0;
  
  root->InitializeSimulation();

  if (is.good()) {
    Solution *s, *prev = NULL;
    do {
      s = dynamic_cast<Solution*>(is.readObject());
      if (s) s->InitializeSimulation();
      if (prev) prev->next = s;
      else solutionList = s;
      prev = s;
    } while (s);
  }
  else 
    return 0;
  is.close();
  
  if (!solutionList) 
    solutionList = new Solution(*root);

  return 1;
}


/* RunSimulation is currently written for a very specific simulation
 * in mind, and therefore will require extensive modification.
 *   Case:	- characterized by a solution present
 *		- a simulation of the effects of plunging the Root object into
 *		  the first solution in the solution list
 *		- the plunge is then followed by an optional temperature 
 *                manipulation
 * RunSimulation determines which case to perform and then does the
 * actual simulation by manipulating the root object.  There is no
 * need to directly interact with any of the child objects since all
 * this is handled by the root object.  At each interation, the root
 * object is either exposed to a specified temperature, or plunged into
 * the first solution (depending on which case is at hand); then, after
 * each ReportInterval iterations, information from the simulation is
 * sent the a file (via the replicas).  The method terminates when
 * endTime is reached.
 */
void RunSimulation() {
  Solution *wasteBin = new Solution(*root);
  REAL initialMass = root->GetTotalMass();
  REAL wasteHeat=0;
  REAL initialEnthalpy = root->GetTotalEnthalpy();
  
  time_t startRTime;
  time(&startRTime);
  time_t lastReportRTime=startRTime;
  
  t=0;
  dt=0;
  int iter = 0;
  int lastReportIter=0;
  REAL lastReportTime=0;
  int mindterrors=0;
  
  REAL T = sp.startTemp>0 ? sp.startTemp : solutionList->GetTemperature();
  
  printf("t(sec),iter,T(K),var0,var1,...\n");
  Report(iter, T);		// one initial report
  
  while (t<sp.endTime) {
    dt = sp.maxdt;
    
    REAL dTdt=0;
    if (((sp.tempRate<0)&&(sp.endTemp<T))||
	((sp.tempRate>0)&&(sp.endTemp>T))) {
      dTdt = sp.tempRate;
      REAL newdt = (sp.endTemp-T)/dTdt;
      if (newdt<dt) 
	dt = newdt;
    }
    
    if ((sp.dendriteBreakdown>0)&&(t>=sp.dendriteBreakdown)) {
      DCompartment *dc = dynamic_cast<DCompartment*>(root);
      JWCompartment *jwc = dynamic_cast<JWCompartment*>(root);
      const DCompartment *dc2 = 
	dynamic_cast<const DCompartment*>(root->GetCompartments());
      if (dc) 
	dc->DendriticBreakdown();
      else if (dc2) 
	((DCompartment*)dc2)->DendriticBreakdown();
      else if (jwc) 
	jwc->Nucleate();
      else
	WARNING("SIMDOER::RunSimulation","dendritic breakdown failed");
      sp.dendriteBreakdown=0;
    }
    
    solutionList->SetTemperature(T);
    solutionList->SetTemperatureRate(dTdt);
    
    whodt=0;
    
    // Step 1
    root->DoRateCalculations(*solutionList);
    
    if (dt<sp.mindt) {
      mindterrors++;
      dt=sp.mindt;
    }
    
    // Step 2
    REAL outHeat;	
    CPSolution outSolution;
    root->FinalizeMovements(outSolution, outHeat);
    
    wasteBin->AddSolute(outSolution->GetSolutes());
    wasteHeat += outHeat + outSolution->GetTotalEnthalpy();
    
    t += dt;	
    iter++;		
    T += dTdt*dt;
    
    // report
    if ( ((sp.reportIter>0)&&(iter-lastReportIter>=sp.reportIter))||
	 ((sp.reportTime>0)&&(t-lastReportTime>=sp.reportTime)) ) {
      fprintf(status,"                                                                              \r");
      
      if (mindterrors)
	fprintf(stderr,"WARNING in SIMDOER::RunSimulation, minimum dt exceeded %d times (%.0f\%)\n",
		mindterrors, (REAL)mindterrors*100/(iter-lastReportIter));
      mindterrors=0;
      
      Report(iter, T);
      
      fprintf(status, "%0.2f/%g ss (%.0f\%)", t, sp.endTime, t*100/sp.endTime);
      REAL ips = (iter-lastReportIter)/(t-lastReportTime);
      if (ips<1e6)
	fprintf(status, ", %.0f iters/ss", ips);
      else 
	fprintf(status, ", %.01g iters/ss", ips);
      
      time_t RTime;  time(&RTime);
      if (RTime!=lastReportRTime) {
	REAL ips = (iter-lastReportIter)/(RTime-lastReportRTime);
	fprintf(status, ", %.0f iters/sec", ips);
      }
      
      fprintf(status, "\r");
      lastReportRTime=RTime;
      lastReportTime=t;
      lastReportIter=iter;
    }
  }
  
  fprintf(status,"                                                                              \r");
  
  if (iter>lastReportIter)
    Report(iter, T);	// one final report
  
  // final status
  REAL massChange = root->GetTotalMass()+wasteBin->GetTotalMass()-initialMass;
  if (ABS(massChange/initialMass)>1e-10) {
    WARNING("SIMDOER::RunSimulation", "mass not conserved");
    fprintf(stderr, "  initial mass %gg, change %+.04f\%\n", initialMass,
	    ABS(massChange/initialMass)*100);
    printf("MASS ERROR, initial mass %gg, change %+.04f\%\n", initialMass,
	   ABS(massChange/initialMass)*100);
  }
  
  REAL heatChange = root->GetTotalEnthalpy()+wasteHeat-initialEnthalpy;
  if (ABS(heatChange/initialEnthalpy)>1e-10) {
    WARNING("SIMDOER::RunSimulation", "energy not conserved");
    fprintf(stderr, "  initial enthalpy %gJ, change %+.04f\%\n", 
	    initialEnthalpy, ABS(heatChange/initialEnthalpy)*100);
    printf("HEAT ERROR,initial enthalpy %gJ,change %+.04f\%\n", 
	   initialEnthalpy, ABS(heatChange/initialEnthalpy)*100);
  }
  
  fprintf(stderr, "\n");
  fprintf(stderr, "Total Sim Time   : %.01f ss\n", t);
  
  time_t RTime;	
  time(&RTime);	
  time_t runTime = RTime-startRTime;
  if (runTime>3600)
    fprintf(stderr, "Total Real Time  : %.01f hr\n", (REAL)runTime/3600);
  else if (runTime>100)
    fprintf(stderr, "Total Real Time  : %.01f min\n", (REAL)runTime/60);
  else
    fprintf(stderr, "Total Real Time  : %d sec\n", runTime);
  
  fprintf(stderr, "Average Sim Rate : %.0f iters/ss\n", iter/t);
  if (runTime>0)
    fprintf(stderr, "Average Run Rate : %d iters/sec\n", iter/runTime);
  else
    fprintf(stderr, "Average Run Rate : fast\n");
  fprintf(stderr, "Total Iterations : %d\n", iter);
  
  delete wasteBin;
}


/* disposes of the simulation objects.  Note that disposing of Root
 * automatically disposes all Child objects associated with Root.
 * The solutions are disposed of, one at a time.  Also, the report
 * file is closed.
 */
void DisposeSimulation() {
  if (root) 
    delete root;  
  root = NULL;
  if (fout) 
    delete fout;  
  fout = NULL;

  if (solutionList) {
    Solution *next, *s = solutionList;
    while (s) {
      next = s->next;
      delete s;
      s = next;
    }
    solutionList = NULL;
  }
  
  if (REALlist) 
    delete[] REALlist;		
  REALlist=0;
  if (INT32list) 
    delete[] INT32list;	
  INT32list=0;
  }

void usage() {
  fprintf(status,"Usage:\n");
  fprintf(status,"  simdoer [-llogfile] [-eextradatafile] simfile [> datafile]\n");
  exit(1);
}

main(int argc, char*argv[]) {
  status = fopen("con", "wt");
  if (!status) status=stderr;
  FILE *log=0;
  extraData=0;
  
  int arg=1;
  while((arg<argc)&&(argv[arg][0]=='-')) {	// option
    switch(argv[arg][1]) {
    case 'l': {
      log = freopen(argv[arg]+2, "wt", stderr);
      if (!log) {
	fprintf(stdout,"ERROR in SIMDOER::main, unable to open log file\n");
	exit(1);
      }
    }
    break;

    case 'e': {
      extraData = fopen(argv[arg]+2, "wt");
      if (!extraData) {
	ERROR("SIMDOER::main", "unable to open extra data file");
	exit(1);
      }
    }
    break;
    
    default:
      usage();
    }
    arg++;
  }
  
  if (arg>=argc)
    usage();
  
  fprintf(status,"Loading Simulation %s...\n", argv[arg]);
  if (!LoadSimulation(argv[arg])) {
    ERROR("SIMDOER::main", "simulation file cannot be loaded");
    exit(1);
  }

  fprintf(status,"Running Simulation...\n");
  RunSimulation();
  
  fprintf(status,"Disposing of Simulation...\n");
  DisposeSimulation();
  
  if (log) fclose(log);
  if (extraData) fclose(extraData);
  
  fprintf(status,"Done.\n");
  return 0;
  }

