/* Fesslix - Stochastic Analysis
 * Copyright (C) 2010-2016 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/>. 
 */

#include "flxrandomFEM.h"

const tdouble *flxElement_base::LX = NULL;
const tdouble *flxElement_base::GX = NULL;
FlxRFBox *flxElement_base::RFbox = NULL;


const FlxElementInfo::elInfo FlxElementInfo::get_elInfo(const std::string& strV, bool errSerious)
{
  if (strV == "index" ) {
    return Index;
  } else if (strV == "lx" ) {
    return lx;
  } else if (strV == "ly" ) {
    return ly;
  } else if (strV == "lz" ) {
    return lz;
  } else if (strV == "x" ) {
    return gx;
  } else if (strV == "y" ) {
    return gy;
  } else if (strV == "z" ) {
    return gz;
  } else if (strV == "w" ) {
    return w;
  } else if (strV == "wx" ) {
    return wx;
  } else if (strV == "wy" ) {
    return wy;
  } else if (strV == "wz" ) {
    return wz;
  } else if (strV == "wx_l" ) {
    return wx_l;
  } else if (strV == "wy_l" ) {
    return wy_l;
  } else if (strV == "wz_l" ) {
    return wz_l;
  } else if (strV == "rx" ) {
    return rx;
  } else if (strV == "ry" ) {
    return ry;
  } else if (strV == "rz" ) {
    return rz;
  } else if (strV == "e" ) {
    return E;
  } else if (strV == "nu" ) {
    return nu;
  } else if (strV == "rho" ) {
    return rho;
  } else if (strV == "g" ) {
    return G;
  } else if (strV == "lambda" ) {
    return lambda;
  } else if (strV == "a" ) {
    return A;
  } else if (strV == "iy" ) {
    return Iy;
  } else if (strV == "iz" ) {
    return Iz;
  } else if (strV == "it" ) {
    return It;
  } else if (strV == "t" ) {
    return t;
  } else if (strV == "n" ) {
    return NormalF;
  } else if (strV == "mx" ) {
    return Moment_x;
  } else if (strV == "my" ) {
    return Moment_y;
  } else if (strV == "mz" ) {
    return Moment_z;
  } else if (strV == "sigma_max" ) {
    return sigma_max;
  } else if (strV == "sigma_min" ) {
    return sigma_min;
  } else {
    std::ostringstream ssV;
    ssV << "Unkown type of element information '" << strV << "'.";
    FlxError(errSerious,"FlxElementInfo::get_elInfo_1", ssV.str() ); return Index; // dummy-return
  }
}

const std::string FlxElementInfo::get_elInfo(const FlxElementInfo::elInfo eI)
{
  switch (eI) {
    case Index:
      return "index";
    case lx:
      return "lx";
    case ly:
      return "ly";
    case lz:
      return "lz";
    case gx:
      return "x";
    case gy:
      return "y";
    case gz:
      return "z";
    case w:
      return "w";
    case wx:
      return "wx";
    case wy:
      return "wy";
    case wz:
      return "wz";
    case wx_l:
      return "wx_l";
    case wy_l:
      return "wy_l";
    case wz_l:
      return "wz_l";
    case rx:
      return "rx";
    case ry:
      return "ry";
    case rz:
      return "rz";
    case E:
      return "E";
    case nu:
      return "nu";
    case rho:
      return "rho";
    case G:
      return "G";
    case lambda:
      return "lambda";
    case A:
      return "A";
    case Iy:
      return "Iy";
    case Iz:
      return "Iz";
    case It:
      return "It";
    case t:
      return "t";
    case NormalF:
      return "N";
    case Moment_x:
      return "mx";
    case Moment_y:
      return "my";
    case Moment_z:
      return "mz";
    case sigma_max:
      return "sigma_max";
    case sigma_min:
      return "sigma_min";
    default:
      #if FLX_DEBUG
	throw FlxException_Crude("FlxElementInfo::get_elInfo_2");
      #endif
      return "";
  }
}


// ------------------------------ Element - List  ----------------------------------------------------

void flxElementList::plot(std::ostream& sout, const std::vector< FlxElementInfo::elInfo >& V, FlxMtxConstFun* ppdim, FlxSMtx* elmtx, const int prec, const int fixW)
{
  if (elmtx==NULL) {
    for (std::map<tuint, flxElement_base*>::iterator pos = box.begin(); pos != box.end(); ++pos) {
      pos->second->plot(pos->first,sout,V,ppdim,prec,fixW);
    }
  } else {
    tnlong idx;
    tdouble idxD;
    flxElement_base* el;
    for (tuint i=0; i<elmtx->get_ncols(); ++i) {
      for (tuint j=0;j<elmtx->get_nrows(); ++j) {
	idxD = elmtx->operator()(j,i);
	if (idxD<0) {
	  std::ostringstream ssV_2;
	  ssV_2 << "Index of an element must not be smaller than zero.";
	  throw FlxException("flxElementList::plot", ssV_2.str() );
	}
	idx = tnlong(idxD);
	el = get(idx);
	el->plot(idx,sout,V,ppdim,prec,fixW);
      }
    }
  }
}

flxElement_base* flxElementList::get(const tuint index, bool errSerious) const
{
  std::map<tuint, flxElement_base*>::const_iterator pos = box.find(index);
  if ( pos != box.end() ) {
    return pos->second;
  } else {
    std::ostringstream ssV;
    ssV << "Element '" << index << "' has not been defined.";
    FlxError(errSerious,"flxElementList::get", ssV.str() ); return NULL; // dummy-return
  }
}

void flxElementList::insert(const tuint index, flxElement_base* element)
{
  std::pair<tuint, flxElement_base*>Element(index, element);
  if ( ! box.insert(Element).second ) {;
    std::ostringstream ssV;
    ssV << "Element '" << index << "' has already been defined.";
    throw FlxException("flxElementList::insert_1", ssV.str() );
  }
}


// ------------------------------ Element_base ----------------------------------------------------

const bool flxElement_base::get_el_info(const tuint index, const tdouble& lx, const tdouble& ly, const tdouble& lz, const std::vector< FlxElementInfo::elInfo >& cVec, flxVec& rVec) const
{
  #if FLX_DEBUG
    if (cVec.size() != rVec.get_N()) {
      throw FlxException_Crude("flxElement_base::get_el_info_1" );
    }
  #endif
  activate_geoEl();
  update_globalConst(lx,ly,lz);
  if (!is_in_domain()) return false;
  bool w_set = false;
  tdouble w_displ[3];
  for (size_t i=0; i<cVec.size(); ++i) {
    switch (cVec[i]) {
      case FlxElementInfo::Index:
	rVec[i] = tdouble(index);
	break;
      case FlxElementInfo::lx:
	rVec[i] = lx;
	break;
      case FlxElementInfo::ly:
	rVec[i] = ly;
	break;
      case FlxElementInfo::lz:
	rVec[i] = lz;
	break;
      case FlxElementInfo::gx:
	rVec[i] = *GX;
	break;
      case FlxElementInfo::gy:
	rVec[i] = *(GX+1);
	break;
      case FlxElementInfo::gz:
	rVec[i] = *(GX+2);
	break;
      case FlxElementInfo::E:
	rVec[i] = material->get_E();
	break;
      case FlxElementInfo::nu:
	rVec[i] = material->get_nu();
	break;
      case FlxElementInfo::rho:
	rVec[i] = material->get_rho();
	break;
      case FlxElementInfo::G:
	rVec[i] = material->get_G();
	break;
      case FlxElementInfo::lambda:
	rVec[i] = material->get_lambda();
	break;
      case FlxElementInfo::w:
      case FlxElementInfo::wx:
      case FlxElementInfo::wy:
      case FlxElementInfo::wz:
	if (!w_set) {
	  get_displacement_vector(w_displ,lx,ly,lz);
	  w_set = true;
	}
	switch(cVec[i]) {
	  case FlxElementInfo::w:
	    rVec[i] = Norm2(w_displ,3);
	    break;
	  case FlxElementInfo::wx:
	    rVec[i] = w_displ[0];
	    break;
	  case FlxElementInfo::wy:
	    rVec[i] = w_displ[1];
	    break;
	  case FlxElementInfo::wz:
	    rVec[i] = w_displ[2];
	    break;
	  default:
	    break;
	}
	break;
      default:
	rVec[i] = get_el_info_help(cVec[i]);
    }
  }
  return true;
}

void flxElement_base::get_displacement_vector(tdouble *w, const tdouble& lx, const tdouble& ly, const tdouble& lz) const
{
  throw FlxException_NotImplemented("flxElement_base::get_displacement_vector");
}

const tdouble flxElement_base::get_el_info_help(const FlxElementInfo::elInfo eI) const
{
  throw FlxException_NotImplemented("flxElement_base::get_el_info_help");
}

void flxElement_base::plot(const tuint idx, std::ostream& os, const std::vector< FlxElementInfo::elInfo >& V, FlxMtxConstFun* ppdim, const int prec, const int fixW) const
{
  activate_geoEl();
  ConstantBox->insert("g_fem_elindex",tdouble(idx));
  const tuint ed = get_dim();
  const iVec ppdim_help = ConstMtxBox->get_Vec_cast2tuint(ppdim->eval(),true);
  if (ppdim_help.size()<ed) {
    std::ostringstream ssV;
    ssV << "The dimension of the vector has to be at least '" << ed << "'.";
    throw FlxException("flxElement_base::plot", ssV.str() );
  }  
  tdouble mlx, mly, mlz;
  const tdouble dl_1 = 2/tdouble(ppdim_help[0]);
  tdouble dl_2,dl_3;
  if (ed>=2) dl_2 = 2/tdouble(ppdim_help[1]);
  if (ed>=3) dl_3 = 2/tdouble(ppdim_help[2]);
  flxVec d(V.size());
  mlx = -ONE;
  for (tuint i = 0; i <= ppdim_help[0]; ++i) {
    if (ed >= 2) {
      mly = -ONE;
      for (tuint j = 0; j <= ppdim_help[1]; ++j) {
	if (ed >= 3) {
	  mlz = -ONE;
	  for (tuint k = 0; k <= ppdim_help[2]; ++k) {
	    if (get_el_info(idx,mlx,mly,mlz,V,d)) {
	      flxVec_simple_plot(os,d,true,prec,fixW); os << std::endl;
	    }
	    mlz+=dl_3;
	  }
	  os << std::endl;
	} else {
	  if (get_el_info(idx,mlx,mly,ZERO,V,d)) {
	    flxVec_simple_plot(os,d,true,prec,fixW); os << std::endl;
	  }
	}
	mly+=dl_2;
      }
      os << std::endl;
    } else {
      if (get_el_info(idx,mlx,ZERO,ZERO,V,d)) {
	flxVec_simple_plot(os,d,true,prec,fixW); os << std::endl;
      }
    }
    mlx+=dl_1;
  }  
  os << std::endl;
}

void flxElement_base::set_Const()
{
  GX = ConstantBox->get("gx",true);
  LX = ConstantBox->get("lx",true);
}

void flxElement_base::set_RFbox(FlxRFBox* RFboxV)
{
  RFbox = RFboxV;
}


// ------------------------------ flxElement_1D_base ----------------------------------------------------

flxElement_1D_base::flxElement_1D_base(flxMaterial* material, flxCrossSec* crossSec, flxEdge& edge)
: flxElement_base(material),next(NULL),edge(edge),crossSec(crossSec)
{
  // link2list
    flxElement_1D_Ptr& elList = edge.get_ElPointer();
    if (elList) {
      next = elList;
    }
    elList = this;
}

flxElement_1D_base::~flxElement_1D_base()
{
  if (next) delete next;
}

void flxElement_1D_base::update_globalConst(const tdouble& lx, const tdouble& ly, const tdouble& lz) const
{
  #if FLX_DEBUG
    if (fabs(ly)>GlobalVar.TOL() || fabs(lz)>GlobalVar.TOL()) {
      throw FlxException("flxElement_1D_base::update_globalConst", "'ly' and 'lz' must be zero.");
    }
  #endif
  edge.update_globalConst(lx);
}

void flxElement_1D_base::get_displacement_vector(tdouble* w, const tdouble& lx, const tdouble& ly, const tdouble& lz) const
{
  tdouble wl[3];
  tdouble T[9];
  wl[0] = edge.get_displacement(FlxDof::dof_wx,lx);
  wl[1] = edge.get_displacement(FlxDof::dof_wy,lx);
  wl[2] = edge.get_displacement(FlxDof::dof_wz,lx);
  edge.get_transformationMatrix(T,true,true);
  for (tuint i=0;i<3;++i) {
    w[i] = 0;
    for (tuint j=0;j<3;++j) {
      w[i]+=T[i*3+j]*wl[j];
    }
  }
}

const tdouble flxElement_1D_base::get_el_info_help(const FlxElementInfo::elInfo eI) const
{
  switch (eI) {
    case FlxElementInfo::wx_l:
      return edge.get_displacement(FlxDof::dof_wx,*LX);
    case FlxElementInfo::wy_l:
      return edge.get_displacement(FlxDof::dof_wy,*LX);
    case FlxElementInfo::wz_l:
      return edge.get_displacement(FlxDof::dof_wz,*LX);
    case FlxElementInfo::A:
      return crossSec->get_A();
    case FlxElementInfo::Iy:
      return crossSec->get_Iy();
    case FlxElementInfo::Iz:
      return crossSec->get_Iz();
    case FlxElementInfo::It:
      return crossSec->get_It();
    default:
      return flxElement_base::get_el_info_help(eI);
  }
}

void flxElement_1D_base::activate_geoEl() const
{
  RFbox->set_Current_Element_KL(&edge);
}

void flxElement_1D_base::assembleStf(const tdouble* meshInterval, const tuint meshInterval_size)
{
  throw FlxException_NotImplemented("flxElement_1D_base::assembleStf");
}

// ------------------------------ flxElement_2D_base ----------------------------------------------------


flxElement_2D_base::flxElement_2D_base(flxMaterial* material, FlxFunDeg* t, flxFace& face)
: flxElement_base(material),next(NULL),face(face),t(t)
{
  // link2list
    flxElement_2D_Ptr& elList = face.get_ElPointer();
    if (elList) {
      next = elList;
    }
    elList = this;
}

flxElement_2D_base::~flxElement_2D_base()
{
  if (next) delete next;
  delete t;
}

void flxElement_2D_base::update_globalConst(const tdouble& lx, const tdouble& ly, const tdouble& lz) const
{
  #if FLX_DEBUG
    if (fabs(lz)>GlobalVar.TOL()) {
      throw FlxException("flxElement_2D_base::update_globalConst", "'lz' must be zero.");
    }
  #endif
  face.update_globalConst(lx,ly);
}

void flxElement_2D_base::get_displacement_vector(tdouble* w, const tdouble& lx, const tdouble& ly, const tdouble& lz) const
{
  w[face.get_mapL2G(0)] = face.get_displacement(FlxDof::dof_wx,lx,ly)*(face.get_mapL2G_Orient(0)?ONE:-ONE);
  w[face.get_mapL2G(1)] = face.get_displacement(FlxDof::dof_wy,lx,ly)*(face.get_mapL2G_Orient(1)?ONE:-ONE);
  w[face.get_mapL2G(2)] = face.get_displacement(FlxDof::dof_wz,lx,ly)*(face.get_mapL2G_Orient(2)?ONE:-ONE);
}

const tdouble flxElement_2D_base::get_el_info_help(const FlxElementInfo::elInfo eI) const
{
  switch (eI) {
    case FlxElementInfo::wx_l:
      return face.get_displacement(FlxDof::dof_wx,*LX,*(LX-1));
    case FlxElementInfo::wy_l:
      return face.get_displacement(FlxDof::dof_wy,*LX,*(LX-1));
    case FlxElementInfo::wz_l:
      return face.get_displacement(FlxDof::dof_wz,*LX,*(LX-1));
    case FlxElementInfo::t:
      return t->get_fun()->cast2positive();
    default:
      return flxElement_base::get_el_info_help(eI);
  }
}

const bool flxElement_2D_base::is_in_domain() const
{
  return (face.get_geofun_calc()+GlobalVar.TOL()>=0.0);
}

void flxElement_2D_base::activate_geoEl() const
{
  RFbox->set_Current_Element_KL(&face);
}

void flxElement_2D_base::assembleStf(const tuint meshIntLx, const tuint meshIntLy, tuint** mesh, tdouble** mesh_cc, const tdouble* meshXLx, const tdouble* meshYLy, flxStiffnessMtxBase& stfmtx)
{
  throw FlxException_NotImplemented("flxElement_2D_base::assembleStf");
}

void flxElement_2D_base::assembleStfHfem(flxStiffnessMtxBase& stfmtx)
{
  throw FlxException_NotImplemented("flxElement_2D_base::assembleStfHfem");
}


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

flxFun_DegreeDetect::flxFun_DegreeDetect(const flxFun_DegreeDetect& ref)
: do_autoDetect(ref.do_autoDetect), degmax(new FlxFunction(*ref.degmax)), testmax(NULL), errmax(NULL)
{
  if (ref.testmax!=NULL) testmax = new FlxFunction(*ref.testmax);
  if (ref.errmax!=NULL) errmax = new FlxFunction(*ref.errmax);
}

flxFun_DegreeDetect& flxFun_DegreeDetect::operator=(const flxFun_DegreeDetect& ref)
{
  if (this!=&ref) {
    delete testmax;
    delete errmax;
    delete degmax;
    do_autoDetect = ref.do_autoDetect;
    degmax = new FlxFunction(*ref.degmax);
    if (ref.testmax!=NULL) {
      testmax = new FlxFunction(*ref.testmax);
    } else {
      testmax = NULL;
    }
    if (ref.errmax!=NULL) {
      errmax = new FlxFunction(*ref.errmax);
    } else {
      errmax = NULL;
    }
  }
  return *this;
}

flxFun_DegreeDetect::flxFun_DegreeDetect(bool do_autoDetect, FlxFunction* degmax, FlxFunction* testmax, FlxFunction* errmax)
: do_autoDetect(do_autoDetect), degmax(degmax), testmax(testmax), errmax(errmax)
{

}

flxFun_DegreeDetect::flxFun_DegreeDetect(bool errSerious)
: do_autoDetect(false), degmax(NULL), testmax(NULL), errmax(NULL)
{
  if (reader->whatIsNextChar()=='(') {
    reader->getChar('(');
    do_autoDetect = reader->getBool(errSerious);
    reader->getChar(',');
    degmax = new FlxFunction(funReader,errSerious);
    if (do_autoDetect) {
      reader->getChar(',');
      testmax = new FlxFunction(funReader,errSerious);
      reader->getChar(',');
      errmax = new FlxFunction(funReader,errSerious);
    }
    reader->getChar(')');
  } else {
    degmax = new FlxFunction(funReader,errSerious);
    do_autoDetect=false;
  }
}

flxFun_DegreeDetect::~flxFun_DegreeDetect()
{
  if (degmax!=NULL) delete degmax;
  if (testmax!=NULL) delete testmax;
  if (errmax!=NULL) delete errmax;
}

const bool flxFun_DegreeDetect::do_detection(tuint& deg, FunBase* funI, const tdouble& coord_lx_start, const tdouble& coord_lx_end)
{
  if (do_autoDetect) {
    tuint GPmax = GI->pDegree2GPs(degmax->cast2tuint());
    tuint GPtest = GI->pDegree2GPs(testmax->cast2tuint());
    tdouble err = errmax->cast2positive();
    
    // check if the function is constant
      if (!ConstantBox->dependOn_GlobalSpatialVar(funI)) {
	deg = 0;	// degree
	return true;
      }
    
    // do a numerical detection
    tuint pri = current_RF_mode_get();
    current_RF_mode_set(-2);	// required for random fields
    deg = FlxFun_EstimateGaussPoint(funI,ConstantBox->get("lx"),coord_lx_start,coord_lx_end,GPmax,GPtest,err,*GI);
    current_RF_mode_set(pri);
    if (deg>=GPmax+1) {	// integral cannot be approximated with up to the requested number of Gauss points
      deg = degmax->cast2tuint();
      return false;
    } else if (deg == 0) {
      return true;
    } else {
      deg = GI->GPs2pDegree(deg);
      return true;
    }
  } else {
    deg = degmax->cast2tuint();
    return true;
  }
}

std::string flxFun_DegreeDetect::write()
{
  if (testmax==NULL || errmax == NULL) {
    return degmax->write();
  } else {
    std::string strV = "(";
    if (do_autoDetect) {
      strV+="true";
    } else {
      strV+="true";
    }
    strV+=",";
    strV+=degmax->write();
    strV+=",";
    strV+=testmax->write();
    strV+=",";
    strV+=errmax->write();
    strV+=")";
    return strV;
  }
}


