#include "dolfin.h"

#include "Display.hh"
#include "Terminal.hh"
//#include "Curses.hh"
#include "Grid.hh"

#include <ProblemPoisson.hh>
#include <ProblemConvDiff.hh>
#include <ProblemNS.hh>

#include <SettingsPoisson.hh>
#include <SettingsConvDiff.hh>
#include <SettingsNavierStokes.hh>

// Temporary for testing
#include "FiniteElement.hh"
#include "TetLinFunction.hh"
#include "TetLinSpace.hh"
#include "TriLinFunction.hh"
#include "TriLinSpace.hh"

// Database for all parameters
Settings *settings = NULL;

// The grid
Grid *grid;

// The problem
Problem *problem;
enum ProblemType { none, poisson, ns, convdiff };
ProblemType problemtype = none;

// Display is by default initialized as Terminal, since we need to be able
// to print messages before the initialization of the parameters, which
// in turn determine the type of Display.
Display *display = new Terminal(DOLFIN_INITIAL_DEBUG_LEVEL);

static bool initialized = false;

//-----------------------------------------------------------------------------
void dolfin_init(int argc, char **argv)
{
  // Check if we have already initialized
  if ( initialized ){
	 display->Warning("Already initialized");
	 return;
  }
  
  // Check that we have specified the problem
  if ( !settings )
 	 display->Error("Problem must be specified before initializing.");
  
  // Do this only once
  initialized = true;
  
  // Write a comment
  display->Message(0,"Initializing DOLFIN.");

  // Initialize all settings
  settings->Initialize();
  
  // Initialize the display
  int debug_level;
  dolfin_get_parameter("debug level",&debug_level);
  char display_type[DOLFIN_LINELENGTH];
  dolfin_get_parameter("display",display_type);
  if ( strcasecmp(display_type,"terminal") == 0 ){
	 delete display;
	 display = new Terminal(debug_level);
  }
  else if ( strcasecmp(display_type,"curses") == 0 ){
	 delete display;

	 display->Warning("Curses display not available. Using \"terminal\".");
         display = new Terminal(debug_level);

	//	 display = new Curses(debug_level);
  }
  else{
	 delete display;
	 display = new Terminal(debug_level);
	 display->Warning("Unknown display type \"%s\". Using \"terminal\".",display_type);
  }

  // Initialise the grid
  grid = new Grid;
  char gridfile[DOLFIN_LINELENGTH];
  dolfin_get_parameter("grid file",gridfile);
  grid->Read(gridfile);
  grid->Display();
  grid->Init();

  // Initialize problem
  switch( problemtype ){
  case poisson:
    problem = new ProblemPoisson(grid);
    break;
  case ns:
    problem = new ProblemNS(grid);
    break;
  case convdiff:
    problem = new ProblemConvDiff(grid);
    break;
  default:
    display->InternalError("dolfin_init()","Unknown problem type: %d",problemtype);
  }
  
}
//-----------------------------------------------------------------------------
void dolfin_end()
{
  // Check that we have called the init function before this
  if ( !initialized )
	 display->Error("You must call dolfin_init() before dolfin_end().");

  // Delete the grid
  delete grid;

  // Delete the settings
  delete settings;

  // Delete the problem
  delete problem;
  
  // Write a comment
  display->Message(0,"Closing DOLFIN.");

  // Delete display
  delete display;
}
//-----------------------------------------------------------------------------
void dolfin_solve()
{
  // Check that we have called the init function before this
  if ( !initialized )
	 display->Error("You must call dolfin_init() before dolfin_solve()."); 
  
  int solve_primal, solve_dual;

  dolfin_get_parameter("solve primal",&solve_primal);
  dolfin_get_parameter("solve dual",&solve_dual);
 
  display->Message(0,"Using module: %s",problem->Description());

  problem->Solve();

  // Solve dual problem
  //if ( solve_dual )
  //dual->Solve();
} 
//-----------------------------------------------------------------------------
void dolfin_save_stability_factors(const char *file_name)
{
  display->Warning("dolfin_save_stability_factors() not implemented yet.");
}
//-----------------------------------------------------------------------------
void dolfin_set_problem(const char *problem)
{
  // Check that we have *not* specified the problem type
  if ( settings )
	 display->Error("Problem is already specified.");
  
  // Go through the list of problems and see which matches
  if ( strcasecmp(problem,"poisson") == 0 ){
	 settings = new SettingsPoisson();
	 problemtype = poisson;
  }
  else if ( strcasecmp(problem,"navier-stokes") == 0 ){
	 settings = new SettingsNavierStokes();
	 problemtype = ns;
  }
  else if ( strcasecmp(problem,"conv-diff") == 0 ){
	 settings = new SettingsConvDiff();
	 problemtype = convdiff;
  }
  else 
	 display->Error("Unknown problem \"%s\".",problem);
}
//-----------------------------------------------------------------------------
void dolfin_set_boundary_conditions(dolfin_bc (*bc)(real x, real y, real z,
														  int node, int component))
{
  // Check that we have specified the problem type
  if ( !settings )
    display->Error("You must specify problem before boundary conditions.");

  settings->bc_function = bc;
}
//-----------------------------------------------------------------------------
void dolfin_set_parameter(const char *identifier, ...)
{
  // Check that we have specified the problem type
  if ( !settings )
	 display->Error("Problem must be specified before setting parameters.");
  
  // Check that we have *not* called the init function before this
  if ( initialized ){
	 display->Warning("Problem already initialized. Unable to change settings.");
	 return;
  }
  
  va_list aptr;
  va_start(aptr,identifier);
  
  settings->SetByArgumentList(identifier,aptr);
  
  va_end(aptr);
}
//-----------------------------------------------------------------------------
void dolfin_get_parameter(const char *identifier, ...)
{
  // Check that we have specified the problem type
  if ( !settings )
	 display->Error("Problem must be specified before accessing parameters.");

  // Check that we have called the init function before this
  if ( !initialized )
	 display->Error("Unable to access parameters before initialization.");
  
  va_list aptr;
  va_start(aptr,identifier);
  
  settings->GetByArgumentList(identifier,aptr);
  
  va_end(aptr);
}
//-----------------------------------------------------------------------------
void dolfin_save_parameters()
{
  // Check that we have called the init function before this
  if ( !initialized ){
	 display->Error("Unable to save parameters before initialization.");
	 return;
  }

  settings->Save();
}
//-----------------------------------------------------------------------------
void dolfin_save_parameters(const char *filename)
{
  // Check that we have called the init function before this
  if ( !initialized ){
	 display->Error("Unable to save parameters before initialization.");
	 return;
  }
  
  settings->Save(filename);
}
//-----------------------------------------------------------------------------
void dolfin_load_parameters()
{
  // Check that we have *not* called the init function before this
  if ( initialized ){
	 display->Warning("Problem already initialized. Unable to load parameters.");
	 return;
  }
    
  settings->Load();
}
//-----------------------------------------------------------------------------
void dolfin_load_parameters(const char *filename)
{
  // Check that we have *not* called the init function before this
  if ( initialized ){
	 display->Warning("Problem already initialized. Unable to load parameters.");
	 return;
  }
  
  settings->Load(filename);
}
//-----------------------------------------------------------------------------
void dolfin_set_function (const char *identifier,
								  real (*f)(real x, real y, real z, real t))
{
  // Check that we have specified the problem type
  if ( !settings )
    display->Error("You must specify problem before setting a function.");

  settings->SetFunction(identifier,f);
}
//-----------------------------------------------------------------------------
void dolfin_test()
{
  cout << "Testing DOLFIN:" << endl;

  settings = new Settings();
  settings->Initialize();
    
  Grid grid;
  grid.Read("../../data/grids/tetgrid_4_4_4.inp");
  grid.Display();
  
  FiniteElement trilin_element(&grid,1,trilin);
  FiniteElement tetlin_element(&grid,1,tetlin);

  TriLinSpace trilinspace(&trilin_element,1);
  TetLinSpace tetlinspace(&tetlin_element,1);
  
  ShapeFunction *v;
  ShapeFunction *w;
  
  v = trilinspace.GetShapeFunction(0);
  w = tetlinspace.GetShapeFunction(1);

  cout << "v*v = " << v[0]*v[0] << endl;
  cout << "w*w = " << w[0]*w[0] << endl;
  cout << "v*w = " << v[0]*w[0] << endl;
  cout << "w*v = " << w[0]*v[0] << endl;
  
  cout << "Test finished." << endl;
}
//-----------------------------------------------------------------------------
void dolfin_test_memory()
{
  // Use this function for testing memory. If this fails (i.e. gives
  // segmentation fault) then something is probably wrong before the call
  // to this function...

  display->Message(0,"DOLFIN memory test...");

  char **test;
  int n = 100;
  
  test = new (char *)[n];
  for (int i=0;i<n;i++){
	 test[i] = new char[DOLFIN_LINELENGTH];
	 sprintf(test[i],"data component %d",i);
  }
  
  for (int i=0;i<n;i++)
	 delete test[i];
  delete test;

  display->Message(0,"Memory test ok.");
}
//-----------------------------------------------------------------------------
