/* Fesslix - Stochastic Analysis
 * Copyright (C) 2010-2014 Wolfgang Betz
 *
 * Fesslix is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fesslix is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Fesslix.  If not, see <http://www.gnu.org/licenses/>. 
 */


#define VERSION_REV 18
#define VERSION_DATE "2015-04-24"

#include "flxdyna.h"
#include "flxdyna_kernel.h"


#if defined(_MSC_VER)
  #include "flxlibdyna_export.h"
#else
  #define FLXLIBDYNA_EXPORT
#endif

#ifdef __WINDOWS__
  #pragma comment(lib, "flxlib")
#endif

flxlibs_interface flx_iface;
flxDynaBox DynaBox;
FlxData* flxdata_ref = NULL;


  

extern "C" FLXLIBDYNA_EXPORT void flxlib_init( FlxObjectReadBox* objReadBox, FlxData* dataBox ) {
  GlobalVar.slog(4) << "  Dyna-library - Version " << VERSION_REV << " (" << VERSION_DATE << ")" << std::endl;
  flx_iface.set_boxes(objReadBox,&(dataBox->FunBox));
  // objects
    flx_iface.add_obj("dyna_new", new FlxObjReadDynaNew()); 
    flx_iface.add_obj("dyna_set", new FlxObjReadDynaSet());
    flx_iface.add_obj("dyna_series", new FlxObjReadDynaSeries());
    flx_iface.add_obj("dyna_series_excite", new FlxObjReadDynaSeriesExcite());
    flx_iface.add_obj("dyna_solve", new FlxObjReadDynaSolve());
    flx_iface.add_obj("dyna_outp", new FlxObjReadDynaOutp());
    flx_iface.add_obj("dyna_vec", new FlxObjReadDynaVec());
  // functions
    flx_iface.add_fun("dyna_natfreq",new FunReadDyna_natfreq());
    flxdata_ref = dataBox;
}

extern "C" FLXLIBDYNA_EXPORT void flxlib_close() {
  flx_iface.free_memory();
}



// ------------------------------------------------------------------------------------------------

FlxObjDynaNew::~FlxObjDynaNew()
{
  delete fname;
  delete fNdof;
  delete fNt;
}

void FlxObjDynaNew::task()
{
  const tuint Ndof = fNdof->cast2tuint(false);
  const tuint Nt = fNt->cast2tuint(false);
  const std::string name = fname->eval_word(true);
  DynaBox.insert(name,Ndof,Nt);
  GlobalVar.slog(4) << "dyna: defined new project '" << name << "' with Ndof=" << Ndof << " and Nt=" << Nt << std::endl;
}

FlxObjBase* FlxObjReadDynaNew::read()
{
  FlxString* fname = new FlxString(false,false);
  FlxFunction* fNdof = NULL;
  FlxFunction* fNt = NULL;
  try {
    reader->getChar('(',false);
    fNdof = new FlxFunction(funReader,false);
    reader->getChar(',',false);
    fNt = new FlxFunction(funReader,false);
    reader->getChar(')',false);
    read_optionalPara(false);
    return new FlxObjDynaNew(get_doLog(),fname,fNdof,fNt);
  } catch (FlxException &e) {
    FLXMSG("FlxObjReadDynaNew::read",1);
    delete fname;
    if (fNdof) delete fNdof;
    if (fNt) delete fNt;
    throw;
  }
}

// ------------------------------------------------------------------------------------------------

FlxObjDynaSet::~FlxObjDynaSet()
{
  delete fname;
  if (fun) delete fun;
  if (fun2) delete fun2;
  if (str) delete str;
}

void FlxObjDynaSet::task()
{
  const std::string name = fname->eval_word(true);
  flxDynaProj& proj = DynaBox.get(name);
  if (id=="dt") {
    const tdouble dt = fun->cast2positive(false);
    proj.set_dt(dt);
    return;
  }
  tuint Ndof = proj.get_Ndof();
  if (id=="f") {
    const tuint Nt = proj.get_Nt();
    if (fun) {
      tdouble& t = data->ConstantBox.getRef(cname);
      FlxMtx& f = proj.get_f();
      tdouble* fp = &(f.operator()(0,0));
      const tuint DOFID = fun2->cast2tuint(false)-1;
      const tdouble t_prev = t;
      if (DOFID>=Ndof) {
	std::ostringstream ssV;
	ssV << "DOF must be an integer between [1;" << Ndof << " ].";
	throw FlxException("FlxObjDynaSet::task_1", ssV.str() ); 
      }
      const tdouble dt = proj.get_dt();
      const tdouble dth = dt/2;
      size_t c = DOFID;
      for (tuint i=0;i<Nt;++i) {
	t = dth + dt*i;
	fp[c] = fun->calc();
	c += Ndof;
      }
      t = t_prev;
      return;
    } else {
      const std::string strv = str->eval_word(true);
      tdouble* dp = data->ConstMtxBox.get_Mtx(Nt,Ndof,strv,true);
      const flxVec dv(dp,Nt*Ndof);
      FlxMtx& f = proj.get_f();
      tdouble* dp2 = f.get_internalPtr(true);
      flxVec dv2(dp2,Nt*Ndof);
      dv2 = dv;
      return;
    }
  }
  if (id=="x0" || id=="v0" || id=="a0" || id=="dratio") {
    const std::string strv = str->eval_word(true);
    tdouble* dp = data->ConstMtxBox.get_Vec(strv,Ndof,true);
    flxVec dv(dp,Ndof);
    if (id=="x0") proj.set_x0(dv);
    if (id=="v0") proj.set_v0(dv);
    if (id=="a0") proj.set_a0(dv);
    if (id=="dratio") proj.set_dratio(dv);
    return;
  }
  if (id=="mass" || id=="stiff" || id=="damp") {
    const std::string strv = str->eval_word(true);
    FlxSMtx* iM = data->ConstMtxBox.get(strv,Ndof,Ndof,true);
    // make sure matrix is allocated
      FlxMtx_baseS* Mb = NULL;
      if (id=="mass") Mb = proj.get_M(true);
      else if (id=="damp") Mb = proj.get_D();
      else Mb = proj.get_K(true);
      if (Mb==NULL) {
	Mb = new FlxMtxSym(Ndof);
	if (id=="mass") proj.set_M(Mb);
	else if (id=="damp") proj.set_D(Mb);
	else proj.set_K(Mb);
      }
    // copy matrix coefficients
      FlxMtxSym* M = dynamic_cast<FlxMtxSym*>(Mb);
      if (M==NULL) throw FlxException("FlxObjDynaSet::task_2","Matrix has wrong type.");
      for (tuint i=0;i<Ndof;++i) {
	for (tuint j=0;j<Ndof;++j) {
	  M->operator()(i,j) = iM->operator()(i,j);
	}
      }
    return;
  }
  std::ostringstream ssV;
  ssV << "Unknown identifier '" << id << "'.";
  throw FlxException("FlxObjDynaSet::task_3", ssV.str() ); 
}

FlxObjBase* FlxObjReadDynaSet::read()
{
  FlxString* fname = new FlxString(false,false);
  FlxFunction* fun = NULL;
  FlxFunction* fun2 = NULL;
  FlxString* str = NULL;
  try {
    reader->getChar(':',false);
    const std::string id = reader->getWord(true,false);
    if (id!="x0" && id!="v0" && id!="a0" && id!="dt" && id!="f" && id!="mass" && id!="stiff" && id!="damp" && id!="dratio" ) {
      std::ostringstream ssV;
      ssV << "Unknown identifier '" << id << "'.";
      throw FlxException("FlxObjReadDynaSet::task", ssV.str() ); 
    }
    std::string cname("");
    if (id=="f") {
      if (reader->whatIsNextChar()=='(') {
      reader->getChar('(');
      cname = reader->getWord(true,false);
      data->ConstantBox.declareC(cname);
      reader->getChar(',');
      fun2 = new FlxFunction(funReader,false);
      reader->getChar(')');
      }
    }
    reader->getChar('=',false);
    if (id=="dt" || (id=="f"&&cname.empty()==false)) fun = new FlxFunction(funReader,false);
    else str = new FlxString(false,false);
    read_optionalPara(false);
    return new FlxObjDynaSet(get_doLog(),fname,id,cname,fun,fun2,str);
  } catch (FlxException &e) {
    FLXMSG("FlxObjReadDynaSet::read",1);
    delete fname;
    if (fun) delete fun;
    if (fun2) delete fun2;
    if (str) delete str;
    throw;
  }
}

// ------------------------------------------------------------------------------------------------

FlxObjDynaSeries::~FlxObjDynaSeries()
{
  delete fname;
  delete fV;
}

void FlxObjDynaSeries::task()
{
  // get the project
    const std::string name = fname->eval_word(true);
    flxDynaProj& proj = DynaBox.get(name);
    tuint Ndof = proj.get_Ndof();
  // get the input vector
    tdouble* Vd = data->ConstMtxBox.get_Vec(fV->eval_word(true),Ndof,true);
  if (mID=="mass") {
    FlxMtx_baseS* Mb = proj.get_M(true);
    if (Mb==NULL) {
      Mb = new FlxMtxDiag(Ndof);
      proj.set_M(Mb);
    }
    FlxMtxDiag* M = dynamic_cast<FlxMtxDiag*>(Mb);
    if (M==NULL) throw FlxException("FlxObjDynaSeries::task_M_1","Mass matrix has wrong type.");
    // set-up the mass matrix
      for (tuint i=0;i<Ndof;++i) {
	M->operator()(i) = Vd[i];
      }
  } else if (mID=="stiff") {
    FlxMtx_baseS* Kb = proj.get_K(true);
    if (Kb==NULL) {
      Kb = new FlxMtxSymBand(Ndof,1);
      proj.set_K(Kb);
    }
    FlxMtxSymBand* K = dynamic_cast<FlxMtxSymBand*>(Kb);
    if (K==NULL) throw FlxException("FlxObjDynaSeries::task_K_1","Stiffness matrix has wrong type.");
    if (K->get_bsize()!=1) throw FlxException("FlxObjDynaSeries::task_K_2","Stiffness matrix has wrong type.");
    // set-up the stiffness matrix
      tdouble* Kv = K->get_VecPointer();
      tnlong n = 0;
      Kv[n] = Vd[0];
      for (tuint i=1;i<Ndof;++i) {
	Kv[n++] += Vd[i];
	Kv[n++] = -Vd[i];
	Kv[n++] = -Vd[i];
	Kv[n] = Vd[i];
      }
  } else if (mID=="damp") {
    FlxMtx_baseS* Db = proj.get_D();
    if (Db==NULL) {
      Db = new FlxMtxSymBand(Ndof,1);
      proj.set_D(Db);
    }
    FlxMtxSymBand* D = dynamic_cast<FlxMtxSymBand*>(Db);
    if (D==NULL) throw FlxException("FlxObjDynaSeries::task_D_1","Damping matrix has wrong type.");
    if (D->get_bsize()!=1) throw FlxException("FlxObjDynaSeries::task_D_2","Damping matrix has wrong type.");
    // set-up the stiffness matrix
      tdouble* Dv = D->get_VecPointer();
      tnlong n = 0;
      Dv[n] = Vd[0];
      for (tuint i=1;i<Ndof;++i) {
	Dv[n++] += Vd[i];
	Dv[n++] = -Vd[i];
	Dv[n++] = -Vd[i];
	Dv[n] = Vd[i];
      }
  } else {
    throw FlxException("FlxObjDynaSeries::task_1","Unknown key '" + mID + "'.");
  }
}

FlxObjBase* FlxObjReadDynaSeries::read()
{
  FlxString* fname = new FlxString(false,false);
  FlxString* fV = NULL;
  try {
    reader->getChar('(',false);
    const std::string mID = reader->getWord(true,false);
    reader->getChar('=',false);
    fV = new FlxString(false,false);
    reader->getChar(')',false);
    read_optionalPara(false);
    return new FlxObjDynaSeries(get_doLog(),fname,mID,fV);
  } catch (FlxException &e) {
    FLXMSG("FlxObjReadDynaSeries::read",1);
    delete fname;
    if (fV) delete fV;
    throw;
  }
}

// ------------------------------------------------------------------------------------------------

FlxObjDynaSeriesExcite::~FlxObjDynaSeriesExcite()
{
  delete fname;
  delete rpevstr;
  delete fM;
  if (fun) delete fun;
}

void FlxObjDynaSeriesExcite::task()
{
  // get the project
    const std::string name = fname->eval_word(true);
    flxDynaProj& proj = DynaBox.get(name);
    tuint Ndof = proj.get_Ndof();
    tuint Nt = proj.get_Nt();
    const tdouble dt = proj.get_dt();
  if (ue) {
    // get the acceleration matrix
      FlxMtx& a = proj.get_a();
      tdouble* ap = &(a.operator()(0,0));
      FlxMtx& v = proj.get_v();
      tdouble* vp = &(v.operator()(0,0));
      FlxMtx& x = proj.get_x();
      tdouble* xp = &(x.operator()(0,0));
    flxVec hv(Ndof);
    // store initial values
      hv = flxVec(vp,Ndof);
      v.set_zeroMtx();
      hv = flxVec(xp,Ndof);
      x.set_zeroMtx(); 
    if (fun) {
      tdouble& t = data->ConstantBox.getRef(rpevstr->eval_word(true));
      const tdouble t_prev = t;
      const tdouble dth = dt/2;
      register tnlong c = Ndof;
      for (tuint i=0;i<Nt;++i) {
	t = dth + dt*i;
	hv = fun->calc();
	flxVec tv(ap+c,Ndof);
	tv += hv;
	c += Ndof;
      }
      t = t_prev;
    } else {
      // root point excitation
	tdouble* rpe = data->ConstMtxBox.get_Vec(rpevstr->eval_word(true),Nt,true);
      register tnlong c = Ndof;
      for (tuint i=0;i<Nt;++i) {
	hv = rpe[i];
	// acceleration
	  flxVec tv(ap+c,Ndof);
	  tv += hv;
	  hv = tv;
	// velocity
	  flxVec tv_vp(vp+c-Ndof,Ndof);
	  flxVec tv_v(vp+c,Ndof);
	  tv_v = tv_vp;
	  hv *= dt;
	  tv_v += hv;
	// position
	  flxVec tv_xp(xp+c-Ndof,Ndof);
	  flxVec tv_x(xp+c,Ndof);
	  tv_x = tv_vp;
	  tv_x *= dt;
	  hv *= dt/2;
	  tv_x += hv;
	  tv_x += tv_xp;
	c += Ndof;
      }
    }
  } else {
    // mass vector
      tdouble* Md = data->ConstMtxBox.get_Vec(fM->eval_word(true),Ndof,true);
      flxVec Mv(Md,Ndof);
    // force matrix
      FlxMtx& F = proj.get_f();
      tdouble* fp = &(F.operator()(0,0));
    if (fun) {
      tdouble& t = data->ConstantBox.getRef(rpevstr->eval_word(true));
      const tdouble t_prev = t;
      const tdouble dth = dt/2;
      register tnlong c = 0;
      for (tuint i=0;i<Nt;++i) {
	t = dth + dt*i;
	flxVec tv(fp+c,Ndof);
	tv = Mv;
	tv *= -ONE*fun->calc();
	c += Ndof;
      }
      t = t_prev;
    } else {
      // root point excitation
	tdouble* rpe = data->ConstMtxBox.get_Vec(rpevstr->eval_word(true),Nt,true);
      register tnlong c = 0;
      for (tuint i=0;i<Nt;++i) {
	flxVec tv(fp+c,Ndof);
	tv = Mv;
	tv *= -ONE*rpe[i];
	c += Ndof;
      }
    }
  }
}

FlxObjReadDynaSeriesExcite::FlxObjReadDynaSeriesExcite() : FlxObjReadBase(true) 
{
  // unexcite
    AllDefParaBox->insert(new FlxOptionalParaBool(false,"dyna::unexcite"));
    ParaBox.insert("unexcite", "dyna::unexcite" );
}

FlxObjBase* FlxObjReadDynaSeriesExcite::read()
{
  FlxString* fname = new FlxString(false,false);
  FlxString* rpevstr = NULL;
  FlxString* fM = NULL;
  FlxFunction* fun = NULL;
  try {
    reader->getChar('(',false);
    rpevstr = new FlxString(false,false);
    reader->getChar(',',false);
    fM = new FlxString(false,false);
    reader->getChar(')',false);
    if (reader->whatIsNextChar()=='=') {
      reader->getChar('=',false);
      data->ConstantBox.declareC(rpevstr->eval_word(true));
      fun = new FlxFunction(funReader,false);
    }
    read_optionalPara(false);
    return new FlxObjDynaSeriesExcite(get_doLog(),fname,rpevstr,fM,fun,get_optPara_bool("unexcite"));
  } catch (FlxException &e) {
    FLXMSG("FlxObjReadDynaSeriesExcite::read",1);
    delete fname;
    if (rpevstr) delete rpevstr;
    if (fM) delete fM;
    throw;
  }
}

// ------------------------------------------------------------------------------------------------

FlxObjDynaSolve::~FlxObjDynaSolve()
{
  delete fname;
}

void FlxObjDynaSolve::task()
{
  // get the project
    const std::string name = fname->eval_word(true);
    flxDynaProj& proj = DynaBox.get(name);
  if (solve_a) proj.solve_a();
  else proj.solve_x();
}

FlxObjReadDynaSolve::FlxObjReadDynaSolve() : FlxObjReadBase(true) 
{
  // solve_a
    AllDefParaBox->insert(new FlxOptionalParaBool(true,"dyna::solve_a"));
    ParaBox.insert("solve_a", "dyna::solve_a" );
}

FlxObjBase* FlxObjReadDynaSolve::read()
{
  FlxString* fname = new FlxString(false,false);
  try {
    read_optionalPara(false);
    return new FlxObjDynaSolve(get_doLog(),fname,get_optPara_bool("solve_a"));
  } catch (FlxException &e) {
    FLXMSG("FlxObjReadDynaSolve::read",1);
    delete fname;
    throw;
  }
}

// ------------------------------------------------------------------------------------------------

FlxObjDynaOutp::~FlxObjDynaOutp()
{
  delete fname;
}

void FlxObjDynaOutp::outp_xva()
{
  // get the project
    const std::string name = fname->eval_word(true);
    flxDynaProj& proj = DynaBox.get(name);
  tuint N = proj.get_Nt();
  FlxMtx* q = NULL;
  if (xva_str=="x") {
    q = &(proj.get_x());
  } else if (xva_str=="v") {
    q = &(proj.get_v());
  } else if (xva_str=="a") {
    q = &(proj.get_a());
  } else if (xva_str=="f") {
    q = &(proj.get_f());
    --N;
  } 
  if (q==NULL) {
    throw FlxException_Crude("FlxObjDynaOutp::outp_xva");
  }
  const tdouble* const qp = q->get_internalPtr(true);
  const tuint Ndof = proj.get_Ndof();
  const tdouble dt = proj.get_dt();
  const tdouble dth = ((N==proj.get_Nt())?ZERO:(dt/2));
  std::ostream& so = sout();
  size_t c = 0;
  for (tuint i=0;i<=N;++i) {
    so << GlobalVar.Double2String(i*dt+dth,false,-1,0);
    for (tuint j=0;j<Ndof;++j) {
      so << GlobalVar.Double2String(qp[c++],false,-1,0);
    }
    so << std::endl;
  }
}

void FlxObjDynaOutp::outp_mck()
{
  // get the project
    const std::string name = fname->eval_word(true);
    flxDynaProj& proj = DynaBox.get(name);
  // get the relevant matrix
    FlxMtx_baseS* Q = NULL;
    if (xva_str=="mass") Q = proj.get_M(false);
    if (xva_str=="stiff") Q = proj.get_K(false);
    if (xva_str=="damp") Q = proj.get_D();
    if (Q==NULL) throw FlxException_Crude("FlxObjDynaOutp::outp_mck");
  Q->output_OctaveMtx(sout());
}

void FlxObjDynaOutp::task()
{
  if (xva_str=="x"||xva_str=="v"||xva_str=="a"||xva_str=="f") outp_xva();
  else if (xva_str=="mass"||xva_str=="stiff"||xva_str=="damp") outp_mck();
  else throw FlxException("FlxObjDynaOutp::task","Unknown identifier (" + xva_str + ")");
}

FlxObjBase* FlxObjReadDynaOutp::read()
{
  FlxString* fname = new FlxString(false,false);
  try {
    reader->getChar('(',false);
    const std::string xva = reader->getWord(true,false);
    reader->getChar(')',false);
    read_optionalPara(false);
    return new FlxObjDynaOutp(get_doLog(),get_stream(),fname,xva);
  } catch (FlxException &e) {
    FLXMSG("FlxObjReadDynaOutp::read",1);
    delete fname;
    throw;
  }
}

// ------------------------------------------------------------------------------------------------

FlxObjDynaVec::~FlxObjDynaVec()
{
  delete fname;
  delete mtxconstname;
  delete dof_fun;
}

void FlxObjDynaVec::task()
{
  // get the project
    const std::string name = fname->eval_word(true);
    flxDynaProj& proj = DynaBox.get(name);
  const tuint DOF = dof_fun->cast2tuint();
  // get the vector to store the result in
    tuint N = proj.get_Nt();
    tdouble* resp = data->ConstMtxBox.get_Vec(mtxconstname->eval_word(true),N);
  const tuint Ndof = proj.get_Ndof();
  if (DOF>Ndof) {
    std::ostringstream ssV;
    ssV << "DOF must be an integer between [1;" << Ndof << " ].";
    throw FlxException("FlxObjDynaVec::task_1", ssV.str() ); 
  }
  FlxMtx* mp = NULL;
  if (id=='x') mp = &(proj.get_x());
  else if (id=='v') mp = &(proj.get_v());
  else if (id=='a') mp = &(proj.get_a());
  else {
    std::ostringstream ssV;
    ssV << "Unknown ID '" << id << "'. Expected 'x', 'v', or 'a'.";
    throw FlxException("FlxObjDynaVec::task_2", ssV.str() ); 
  }
  tdouble* mpp = &(mp->operator()(0,0));
  // assign the result
    size_t c = Ndof+DOF-1;
    for (tuint i=0;i<N;++i) {
      resp[i] = mpp[c];
      c += Ndof;
    }
}

FlxObjBase* FlxObjReadDynaVec::read()
{
  FlxString* mcn = new FlxString(false,false);
  FlxString* fname = NULL;  
  FlxFunction* dof_fun = NULL;
  try {
    reader->getChar('=');
    fname = new FlxString(false,false);
    reader->getChar('(',false);
    char id = reader->getChar();
    if (id=='A') id='a';
    else if (id=='V') id = 'v';
    else if (id=='X') id = 'x';
    else if (id=='a');
    else if (id=='v');
    else if (id=='x');
    else {
      throw FlxException("FlxObjReadDynaVec::read","Unknown ID");
    }
    reader->getChar(',');
    reader->getWord("dof",false);
    reader->getChar('=');
    dof_fun = new FlxFunction(funReader,false);
    reader->getChar(')',false);
    read_optionalPara(false);
    return new FlxObjDynaVec(get_doLog(),fname,id,mcn,dof_fun);
  } catch (FlxException &e) {
    FLXMSG("FlxObjReadDynaOutp::read",1);
    delete mcn;
    if (fname) delete fname;
    if (dof_fun) delete dof_fun;
    throw;
  }
}

// ------------------------------------------------------------------------------------------------


const tdouble FunDyna_natfreq::calc()
{
  // get the project
    flxDynaProj& proj = DynaBox.get(projName);
  const tuint mID = tuint_from(child_1->calc(),"ID of the eigenmode",true,false);
  return proj.get_natfreq(mID-1);
}

const std::string FunDyna_natfreq::write()
{
  std::string str1 = write_v() +"(";
  str1 += projName;
  str1 += ',';
  str1 += child_1->write();
  str1 += ")";
  return str1;
}

FunBase* FunReadDyna_natfreq::read(bool errSerious)
{
  const std::string projName = reader->getWord(true,errSerious);
  reader->getChar(',',errSerious);
  return new FunDyna_natfreq( projName, read_parameters(1,errSerious) );
}



