#include <stdio.h>
#include <math.h>
#include <mem.h>
#include <conio.h>
#include <time.h>
#pragma hdrstop

#include "subint.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 totalTime	3600
#define iterPerSec	1000
#define reportIter	60000

#define numIntervals	20
#define reportInter	1

#define l 	0.1		/* total experiment length (m) */
#define dx	((REAL)l/numIntervals)
#define dt	((REAL)1/iterPerSec)

#define C0	0.150		/* initial concentration (mol/L) */
#define T0	(F+phase_a*C0+phase_b*SQR(C0))	/* initial temperature (K) */

#define Ce	1		/* final concentration (mol/L) */
#define Te      253		/* final temperature (K) */
#define speed	0.1		/* time to 1/e of final value (/sec) */
//#define speed	600

#define phase_a	-3.115	/* phase diagram coeff (K*L/mol) */
#define phase_b	-0.27	/* phase diagram coeff (K*L2/mol2) */

#define HeatDiffusion
#define SaltDiffusion
//#define Checks

//  Global variables

Subinterval interval[numIntervals];
REAL eC=C0;
int errors=0;


REAL ExactSolution(REAL x, REAL t) {
  REAL ssd=sqrt(speed/D), result=0, sign=1;
  x=l-x;
  
  for (int n=0; n<1000; n++) {
    result += sign * exp(-D*SQR(2*n+1)*SQR(M_PI)*t/(4*SQR(l))) * 
      cos((2*n+1)*M_PI*x/(2*l))
      / ((2*n+1) * (4*speed*SQR(l)-D*SQR(M_PI)*SQR(2*n+1)));
    sign = -sign;
  }
  
  result *= -16*speed*SQR(l)/M_PI;
  result += 1 - exp(-speed*t)*cos(x*ssd)/cos(l*ssd);
  
  return Ce * result;
}

inline REAL Tt(REAL t) { 
  return T0 + (Te-T0)*(1-exp(-speed*t)); 
}
//inline REAL Tt(REAL t)
//  { return t==0 ? T0 : T0 + (Te-T0)*exp(-speed/t); }
inline REAL Ct(REAL t) { 
  return C0 + (Ce-C0)*(1-exp(-speed*t)); 
}

int DoSimulation() {
  int iter=0, stop=0, iceI=0;
  REAL t=0;
  
  do {
    t += dt;
    int i;
    
    
    // First:	adjust all T's to reflect diffusion of salt and heat
#ifdef SaltDiffusion
    for (i=0; i<numIntervals; i++)
      if (!interval[i].IsSolid)
	interval[i].AdjustTotal(interval[i].salt, D*dt);
#endif
#ifdef HeatDiffusion
    for (i=0; i<numIntervals; i++)
      interval[i].AdjustTotal(interval[i].heat, 
			      MW*(interval[i].IsSolid ? 
				  sK/sCp/sRho:lK/lCp/lRho)*0.000001*dt);
    
    // and move boundaries as required for ice growth
    for (i=0; i<numIntervals-2; i++)
      if ((interval[i].IsSolid)&&(!interval[i+1].IsSolid)) {
	REAL dX = interval[i].dXdt * dt;
#ifdef Checks
	if (-dX > interval[i].h)
	  fprintf(stderr, "BAD dXdt at iter=%d, i=%d..%d, dXdt=%g\n", 
		  iter, i, i+1, interval[i].dXdt), errors++;
#endif
	
	while (dX >= interval[i+1].h) {
	  dX -= interval[i+1].h;
	  REAL newh = (interval[i].h+interval[i+1].h)/2;
	  REAL moved;
	  if (interval[i].h>newh) {
	    REAL b = interval[i].h/2;
	    REAL a = newh-interval[i].h+b;
	    moved = (b-a)*((SQR(a)+a*b)*interval[i].heat.alpha/3 + 
			   (a+b)*interval[i].heat.beta/2
			   + interval[i].heat.total/interval[i].h);
	  }
	  else {
	    REAL a = -interval[i+1].h/2;
	    REAL b = interval[i+1].h-newh+a;
	    moved = -(b-a)*((SQR(b)+a*b)*interval[i+1].heat.alpha/3 + 
			    (a+b)*interval[i+1].heat.beta/2
			    + interval[i+1].heat.total/interval[i+1].h);
	  }
	  interval[i].heat.total -= moved;
	  interval[i+1].heat.total += moved;
	  
	  interval[i+2].salt.total += interval[i+1].salt.total;
	  interval[i+1].salt.total = interval[i+1].salt.alpha = 
	    interval[i+1].salt.beta = 0;
	  
	  interval[i].h = interval[i+1].h = newh;
	  interval[i+1].x = interval[i].x+interval[i].h;
	  interval[i+1].IsSolid = 1;
	  i++;
	}
	
	REAL moved;
	if (dX<0) {
	  REAL b = interval[i].h/2;
	  REAL a = dX+b;
	  moved = (b-a)*((SQR(a)+a*b)*interval[i].heat.alpha/3 + 
			 (a+b)*interval[i].heat.beta/2
			 + interval[i].heat.total/interval[i].h);
	}
	else {
	  REAL a = -interval[i+1].h/2;
	  REAL b = dX+a;
	  moved = -(b-a)*((SQR(b)+a*b)*interval[i+1].heat.alpha/3 + 
			  (a+b)*interval[i+1].heat.beta/2
			  + interval[i+1].heat.total/interval[i+1].h);
	}
	interval[i].heat.total -= moved;
	interval[i+1].heat.total += moved;
	interval[i].h += dX;
	interval[i+1].h -= dX;
	
	interval[i+1].x = interval[i].x+interval[i].h;
      }
#endif


    // Second:	calculate parameters (P's and Q's)
#ifdef HeatDiffusion
    interval[numIntervals-1].InitialPQFlux(interval[numIntervals-1].heat, 0);
#endif
#ifdef SaltDiffusion
    interval[numIntervals-1].InitialPQFlux(interval[numIntervals-1].salt, 0);
#endif
    REAL A,B;
    for (i=numIntervals-2; i>=0; i--)
      if ((interval[i].IsSolid)&&(!interval[i+1].IsSolid)) {
#ifdef SaltDiffusion
	interval[i].estConc = eC;  iceI=i+1;
	A = phase_a+2*phase_b*interval[i].estConc;
	B = F-phase_b*SQR(interval[i].estConc);
#ifdef Checks
	if (interval[i].estConc<0)
	  fprintf(stderr, "DoSimulation: estConc<0 at iter=%d\n", 
		  iter), errors++;
#endif
	interval[i].CalculatePQXS(interval[i+1], interval[i].estConc, A, B);
#else
        interval[i].CalculatePQX(interval[i].heat);
#endif
      }
      else {
#ifdef HeatDiffusion
	interval[i].CalculatePQ(interval[i].heat, interval[i+1].heat, 
				interval[i+1]);
#endif
#ifdef SaltDiffusion
	if (!interval[i].IsSolid)
	  interval[i].CalculatePQ(interval[i].salt, interval[i+1].salt, 
				  interval[i+1]);
#endif
      }
    

    // Third:	calculate parameters (alpha's and beta's)
#ifdef HeatDiffusion
    interval[0].InitialAlphaBetaFixed(interval[0].heat, Tt(t));
#else
    interval[0].InitialAlphaBetaFixed(interval[0].salt, Ct(t));
#endif
    for (i=1; i<numIntervals; i++)
      if ((interval[i-1].IsSolid)&&(!interval[i].IsSolid)) {
#ifdef SaltDiffusion
	interval[i].CalculateAlphaBetaXS(interval[i-1], interval[i-1].estConc,
					 A, B);
#else
	interval[i].CalculateAlphaBetaX(interval[i].heat, interval[i-1].heat,
					interval[i-1]);
#endif
      }
      else {
#ifdef HeatDiffusion
	interval[i].CalculateAlphaBeta(interval[i].heat, interval[i-1].heat,
				       interval[i-1]);
#endif
#ifdef SaltDiffusion
	if (!interval[i].IsSolid)
	  interval[i].CalculateAlphaBeta(interval[i].salt, interval[i-1].salt,
					 interval[i-1]);
#endif
      }

#ifdef SaltDiffusion
#ifdef HeatDiffusion
    // Estimate concentration at ice front for next iteration
    eC = interval[iceI].salt.alpha*SQR(interval[iceI].h)/6
      -interval[iceI].salt.beta*interval[iceI].h/2
      +interval[iceI].salt.total/interval[iceI].h;
#endif
#endif


    // Done:	report
    if ((iter%reportIter)==0) {
      printf("time %.01f,iter %d\n", t, iter);
      printf("x,h,T,C,IsSolid,dT0dx,dT1dx,dCdx,exactC\n");
      
      i=0;
      do {
	REAL C = interval[i].salt.alpha*SQR(interval[i].h)/6
	  -interval[i].salt.beta*interval[i].h/2
	  +interval[i].salt.total/interval[i].h;
	REAL T = interval[i].heat.alpha*SQR(interval[i].h)/6
	  -interval[i].heat.beta*interval[i].h/2
	  +interval[i].heat.total/interval[i].h;
        if ((i>0)&&(interval[i-1].IsSolid!=interval[i].IsSolid)) {
	  REAL dT0dx = interval[i-1].heat.alpha*interval[i-1].h + 
	    interval[i-1].heat.beta;
	  REAL dT1dx = -interval[i].heat.alpha*interval[i].h + 
	    interval[i].heat.beta;
	  REAL dCdx  = -interval[i].salt.alpha*interval[i].h + 
	    interval[i].salt.beta;
	  printf("%.04f,%.04f,%.03f,%.03f,,%g,%g,%g\n", interval[i].x, 
		 interval[i].h, T, C, dT0dx, dT1dx, dCdx);
	  //          if (T<250) stop=1;
	}
        else
#ifdef HeatDiffusion
	  printf("%.04f,%.04f,%.03f,%.03f,%d\n", interval[i].x, interval[i].h,
		 T, C, interval[i].IsSolid);
#else
  	  printf("%.04f,%.04f,,%.03f,,,,,%.03f\n", interval[i].x,interval[i].h,
		 C, ExactSolution(interval[i].x,t));
#endif

	REAL X = interval[i].x+interval[i].h/2;
	C = interval[i].salt.total/interval[i].h - 
	  SQR(interval[i].h)*interval[i].salt.alpha/12;
#ifdef HeatDiffusion
        T = interval[i].heat.total/interval[i].h - 
	  SQR(interval[i].h)*interval[i].heat.alpha/12;
	printf("%.04f,,%.03f,%.03f,%d\n", X, T, C, interval[i].IsSolid);
#else
	printf("%.04f,,,%.03f,,,,,%.03f\n", X, C, ExactSolution(X,t));
#endif

	i+=reportInter;
      } while (i<numIntervals);

      i = numIntervals-1;
      REAL C = interval[i].salt.alpha*SQR(interval[i].h)/6
	+interval[i].salt.beta*interval[i].h/2
	+interval[i].salt.total/interval[i].h;
#ifdef HeatDiffusion
      REAL T = interval[i].heat.alpha*SQR(interval[i].h)/6
	+interval[i].heat.beta*interval[i].h/2
	+interval[i].heat.total/interval[i].h;
      printf("%.04f,,%.03f,%.03f,%d\n\n", l, T, C, interval[i].IsSolid);
#else
      printf("%.04f,,,%.03f,,,,,%.03f\n\n", l, C, ExactSolution(l,t));
#endif
      
      fprintf(stderr, "Iter: %d (%.0f%%)\r", iter, t*100/totalTime);
      //	  getch();
    }
    
    // Done:	check results
#ifdef Checks
#ifdef HeatDiffusion
    REAL T = SQR(interval[0].h)*interval[0].heat.alpha/6 - 
      interval[0].h*interval[0].heat.beta/2
      + interval[0].heat.total/interval[0].h;
    if (ABS(T-Tt(t)) > 1e-6)
      fprintf(stderr, "BAD BC at iter=%d, x=0, %g!=%g\n", iter, T, 
	      Tt(t)), errors++;
#endif
    
    REAL grad = interval[numIntervals-1].heat.alpha*
      interval[numIntervals-1].h+interval[numIntervals-1].heat.beta;
    if (ABS(grad) > 1e-6)
      fprintf(stderr, "BAD BC at iter=%d, x=L, %g!=0\n", iter, grad), errors++;
    
    for (i=0; i<numIntervals-1; i++)
      if (interval[i].IsSolid==interval[i+1].IsSolid) {
	REAL grad1 = interval[i].heat.alpha*interval[i].h+
	  interval[i].heat.beta;
	REAL grad2 = -interval[i+1].heat.alpha*interval[i+1].h+
	  interval[i+1].heat.beta;
	if (((grad1!=0)||(grad2!=0))&&(ERROR(grad1,grad2)>1e-5))
	  fprintf(stderr, "BAD GRAD at iter=%d, i=%d..%d, %g!=%g\n", iter,i,
		  i+1, grad1, grad2), errors++;
      }
    
      else {
	REAL h3P = interval[i+1].h + 3*interval[i+1].heat.P;
	REAL g3P = interval[i+1].h + 3*interval[i+1].salt.P;
	REAL hP  = interval[i+1].h + interval[i+1].heat.P;
	REAL gP  = interval[i+1].h + interval[i+1].salt.P;
	REAL Tm  = interval[i].heat.total/interval[i].h;
	REAL Tp  = interval[i+1].heat.total/interval[i+1].h;
	REAL Tf  = A*interval[i+1].salt.total/interval[i+1].h + B;
	
        REAL LHS = interval[i+1].h*h3P*interval[i+1].heat.alpha/6 - 
	  interval[i+1].h*A*g3P*interval[i+1].salt.alpha/6;
        REAL RHS = interval[i+1].h*interval[i+1].heat.Q/2 - 
	  interval[i+1].h*interval[i+1].salt.Q*A/2 - Tp + Tf;
        if (ABS(LHS-RHS)>1e-6)
          fprintf(stderr,"BAD BC1: %g != %g\n", LHS, RHS), errors++;
	
        LHS = interval[i].h*interval[i].heat.beta/2 - 
	  interval[i+1].h*A*g3P*interval[i+1].salt.alpha/6;
        RHS = -SQR(interval[i].h)*interval[i].heat.alpha/6 - 
	  interval[i+1].h*interval[i+1].salt.Q*A/2 - Tm + Tf;
        if (ABS(LHS-RHS)>1e-6)
          fprintf(stderr,"BAD BC2: %g != %g\n", LHS, RHS), errors++;

        LHS = sK*interval[i].heat.beta*interval[i].estConc + 
	  lK*hP*interval[i+1].heat.alpha*interval[i].estConc - 
	  D*Lp*gP*interval[i+1].salt.alpha;
        RHS = -sK*interval[i].h*interval[i].heat.alpha*interval[i].estConc - 
	  D*Lp*interval[i+1].salt.Q + 
	  lK*interval[i+1].heat.Q*interval[i].estConc;
        if (ABS(LHS-RHS)>1e-6)
          fprintf(stderr,"BAD BC3: %g != %g\n", LHS, RHS), errors++;
      }
#endif

  } while ((++iter<(iterPerSec*totalTime+1))&&(!stop));

  return iter;
}

main(int, char*[]) {
  for (int i=0; i<numIntervals; i++) {
    interval[i].x = i==0 ? 0 : interval[i-1].x+interval[i-1].h;
#ifdef HeatDiffusion
    interval[i].h = i==0 ? dx/10 : (i==1 ? 19*dx/10 : dx);
#else
    interval[i].h = dx;
#endif
    
    interval[i].heat.alpha = 0;
    interval[i].heat.beta  = 0;
    interval[i].heat.total = T0*interval[i].h;
    
    interval[i].salt.alpha = 0;
    interval[i].salt.beta  = 0;
    
#ifdef HeatDiffusion
    interval[i].salt.total = i==0 ? 0 : C0*interval[i].h;
    interval[i].IsSolid = i==0 ? 1 : 0;
#else
    interval[i].salt.total = C0*interval[i].h;
    interval[i].IsSolid = 0;
#endif
    
    interval[i].dXdt = 0;
  }
  
  time_t t1 = time(NULL);
  int totalIter = DoSimulation();
  time_t t2 = time(NULL);
  
  double sec=difftime(t2, t1);
  fprintf(stderr,"                                          \n");
  fprintf(stderr,"Time:       %.01f sec\nIterations: %d\n",sec,totalIter);
  fprintf(stderr,"Errors:     %d\nIterations per Second: %.01f\n",errors,
	  totalIter/sec);
  
  return 0;
}

