/* 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 FlxDof::dof FlxDof::get_dof(std::string strdof, const bool errSerious)
{
  std::transform(strdof.begin(), strdof.end(), strdof.begin(), (int(*)(int)) std::tolower);
  if (strdof == "wx" ) {
    return dof_wx;
  } else if (strdof == "wy" ) {
    return dof_wy;
  } else if (strdof == "wz" ) {
    return dof_wz;
  } else if (strdof == "rx" ) {
    return dof_rx;
  } else if (strdof == "ry" ) {
    return dof_ry;
  } else if (strdof == "rz" ) {
    return dof_rz;
  } else {
    std::ostringstream ssV;
    ssV << "Unkown DOF '" << strdof << "'.";
    FlxError(errSerious,"get_dof", ssV.str() ); return dof_wx; // dummy-return
  }
}

const FlxDof::dof FlxDof::get_dof(const FlxDof::dir dirV, const bool isForce)
{
  if (isForce) {
    switch (dirV) {
      case x:
	return dof_wx;
      case y:
	return dof_wy;
      case z:
	return dof_wz;
      default:
	#if FLX_DEBUG
	  throw FlxException_Crude("FlxDof::get_dof_1");
	#else
	  return dof_wx; // dummy-return
	#endif
    }
  } else {
    switch (dirV) {
      case x:
	return dof_rx;
      case y:
	return dof_ry;
      case z:
	return dof_rz;
      default:
	#if FLX_DEBUG
	  throw FlxException_Crude("FlxDof::get_dof_2");
	#else
	  return dof_wx; // dummy-return
	#endif
    }
  }
}

const FlxDof::dof FlxDof::get_dof(const tuint dirV)
{
  switch(dirV) {
    case 0:
      return dof_wx;
    case 1:
      return dof_wy;
    case 2:
      return dof_wz;
    case 3:
      return dof_rx;
    case 4:
      return dof_ry;
    case 5:
      return dof_rz;
    default:
    #if FLX_DEBUG
      throw FlxException_Crude("FlxDof::get_dof_3");
    #else
      return dof_wx; // dummy-return
    #endif
  }
}

const std::string FlxDof::get_dof(const FlxDof::dof edof)
{
  switch(edof) {
    case dof_wx:
      return "wx";
      break;
    case dof_wy:
      return "wy";
      break;
    case dof_wz:
      return "wz";
      break;
    case dof_rx:
      return "rx";
      break;
    case dof_ry:
      return "ry";
      break;
    case dof_rz:
      return "rz";
      break;
    default:
      return "";
  }
}

const FlxDof::dir FlxDof::get_dir(const FlxDof::dof edof)
{
  switch (edof) {
    case dof_wx:
      return x;
    case dof_wy:
      return y;
    case dof_wz:
      return z;
    case dof_rx:
      return x;
    case dof_ry:
      return y;
    case dof_rz:
      return z;
    default:
      #if FLX_DEBUG
	throw FlxException_Crude("FlxDof::get_dir_1");
      #else
	return x; // dummy-return
      #endif
  }
}

const FlxDof::dir FlxDof::get_dir(const tuint dirV)
{
  switch(dirV) {
    case 0:
      return x;
    case 1:
      return y;
    case 2:
      return z;
    default:
    #if FLX_DEBUG
      throw FlxException_Crude("FlxDof::get_dir_2");
    #else
      return x; // dummy-return
    #endif
  }
}

const bool FlxDof::is_force(const FlxDof::dof edof)
{
  switch (edof) {
    case dof_wx:
    case dof_wy:
    case dof_wz:
      return true;
    case dof_rx:
    case dof_ry:
    case dof_rz:
      return false;
    default:
    #if FLX_DEBUG
      throw FlxException_Crude("flxELoad::refine_mesh_1");
    #else
      return true; // dummy-return
    #endif
  }
}

void FlxLoadCase::log_info(std::ostream& lout) const
{
  lout << "FEM: inserted load case (index=" << index << ")" << std::endl;
}

FlxLoadCaseBox::~FlxLoadCaseBox()
{
  for (std::map<tuint, FlxLoadCase*>::iterator pos = box.begin(); pos != box.end(); ++pos) delete pos->second;
}

void FlxLoadCaseBox::insert(const tuint index, FlxLoadCase* value, const bool silent)
{
  if (!silent) value->log_info(GlobalVar.slog(4));
  std::pair<tuint, FlxLoadCase*>Element(index, value);
  if ( ! box.insert(Element).second ) {
    std::map<tuint, FlxLoadCase*>::iterator pos = box.find(index);
    *(pos->second) = *value;
    delete value;
  }
}

FlxLoadCase& FlxLoadCaseBox::get(const tuint index, const bool errSerious) const
{
  std::map<tuint, FlxLoadCase*>::const_iterator pos = box.find(index);
  if ( pos != box.end() ) {
    return (*pos->second);
  } else {
    std::ostringstream ssV_2;
    ssV_2 << "The load case '" << index << "' does not exist.";
    FlxError(errSerious,"FlxLoadCaseBox::get", ssV_2.str(), "The load case has not yet been defined.");
    throw FlxException_Crude("FlxLoadCaseBox::get_1");
  }
}

void FlxLoadCaseBox::set_active(const std::vector< tuint >& loadcases)
{
  // deactivate all load cases
    for (std::map<tuint, FlxLoadCase*>::iterator pos = box.begin(); pos != box.end(); ++pos) {
      pos->second->set_active(false);
    }
  // activate the requested load cases
    for (size_t i = 0; i < loadcases.size(); ++i) {
      get(loadcases[i]).set_active(true);
    }
}


flxNLoad* flxNLoad::link2list(flxNLoad* el2link)
{
  if (el2link->dofDir < this->dofDir) {
    el2link->next = this;
    return el2link;
  } else if (el2link->dofDir == this->dofDir) {
    if ( el2link->lc.get_index() < this->lc.get_index() ) {
      el2link->next = this;
      return el2link;
    }
    #if FLX_DEBUG
      else if ( el2link->lc.get_index() == this->lc.get_index() ) {
	throw FlxException_Crude("flxNLoad::link2list");
      } 
    #endif
  }
  
  if (this->next == NULL) {
    this->next = el2link;
  } else {
    this->next = next->link2list(el2link);
  }
  return this;  
}

flxNLoad* flxNLoad::find(const FlxDof::dof dofDirV, const FlxLoadCase& lcV)
{
  if (dofDir == dofDirV && lc.get_index() == lcV.get_index()) return this;
  else if (dofDir < dofDirV || (dofDir == dofDirV && lc.get_index() < lcV.get_index()) ) {
    if (next) return next->find(dofDirV,lcV);
  }
  return NULL;
}

flxNLoad::flxNLoad(const FlxDof::dof dofDir, FlxFunction* value, const FlxLoadCase& lc, flxNLoadPtr& start)
: dofDir(dofDir), value(value), next(NULL), lc(lc)
{
  // find correct position in list
    if (start) {
      start = start->link2list(this);
    } else {
      start = this;
    }
}

flxNLoad::~flxNLoad()
{
  if (next) delete next;
  delete value;
}

void flxNLoad::assembleForces(flxStiffnessMtxBase& stfmtx, flxDOFn* dofList) const
{
  if (lc.is_active()) {
    stfmtx.ForceVec_set_value(dofList->find_dof(dofDir)->get_DOFindex(),lc.get_factor()*value->calc());
  }
  if (next) next->assembleForces(stfmtx,dofList);
}

flxELoad::flxELoad(const FlxDof::dof dofDir, const bool global, FlxFunDeg* value, FlxFunction* pos1, FlxFunction* pos2, const FlxLoadCase& lc, flxELoadPtr& start)
: dofDir(dofDir), global(global), value(value), pos1(pos1), pos2(pos2), next(NULL), lc(lc)
{
  // find correct position in list
    if (start) {
      start = start->link2list(this);
    } else {
      start = this;
    }
}

flxELoad::~flxELoad()
{
  if (next) delete next;
  delete value;
  delete pos1;
  if (pos2) delete pos2;
}

flxELoad* flxELoad::link2list(flxELoad* el2link)
{
  if (el2link->global && !(this->global) ) {	// first global than local
    el2link->next = this;
    return el2link;
  } else if (el2link->global == this->global) {
    if (el2link->dofDir < this->dofDir) {
      el2link->next = this;
      return el2link;
    } else if (el2link->dofDir == this->dofDir) {
      if ( el2link->lc.get_index() <= this->lc.get_index() ) {
	el2link->next = this;
	return el2link;
      }
    }
  }
  
  if (this->next == NULL) {
    this->next = el2link;
  } else {
    this->next = next->link2list(el2link);
  }
  return this;  
}

void flxELoad::integrate_help(flxEdge& edge, const tdouble &lx1, const tdouble &lx2, const tuint elId, const tdouble* meshInterval, const tuint pOrder, tdouble *fv) const
{
  const tuint N=GI->pDegree2GPs(value->get_deg()+pOrder);
  // get Gauss information
    GI->check_GA(N);
    const tdouble* GP = GI->get_GP(N);
    const tdouble* GW = GI->get_GW(N);
  FlxFunction& funi = *value->get_fun();
  flxVec t(pOrder+1);
  tdouble lxt;	// local coordinate of sub-element
  tdouble lx;	// local coordinate of the edge
  for (tuint p=0;p<N;++p) {
    lxt=lx1+(GP[p]+1.0)/2.0*(lx2-lx1);
    lx = meshInterval[elId]+(lxt+1.0)/2.0*(meshInterval[elId+1]-meshInterval[elId]);
    edge.update_globalConst(lx);
    hLP_const(t,lxt,GW[p]*funi.calc());
  }
  t *= (lx2-lx1)/4.0*(meshInterval[elId+1]-meshInterval[elId]);
  const tdouble* tp = t.get_tmp_vptr();
  fv[0] += tp[0];
  fv[pOrder] += tp[1];
  for (tuint i=1;i<pOrder;++i) {
    fv[i] += tp[i+1];
  }
}

void flxELoad::refine_mesh(flxFEMrefine_1D* mesh, flxEdge& edge, const bool* DOFnMap)
{
  // make sure all necessary DOFs are generated
    if (global) {
      const tuint i = FlxDof::get_dir(dofDir);
      const bool isForce = FlxDof::is_force(dofDir);
      for (tuint j=0;j<3;++j) {
	if (DOFnMap[j*3+i]) {
	  flxDOFe* de = edge.find_dof(FlxDof::get_dof(FlxDof::get_dir(j),isForce),true);
	  GI->check_GA_polynomialDegree(de->get_DOFpOrder() + value->get_deg());
	}
      }
    } else {
      flxDOFe* de = edge.find_dof(dofDir,true);
      GI->check_GA_polynomialDegree(de->get_DOFpOrder() + value->get_deg());
    }
  
  // refine the mesh
    if (mesh) {
      if (mesh->get_do_loadrefine()) {
	mesh->insert_point(pos1->calc());
	if (pos2) {
	  mesh->insert_point(pos2->calc());
	}
      }
    }
  if (next) next->refine_mesh(mesh,edge,DOFnMap);
}

void flxELoad::assembleForces(flxEdge& edge, const tdouble* meshInterval, const tuint meshInterval_size) const
{
  if (lc.is_active()) {
    if (pos2) {		// line load
      if (global) {
	throw FlxException_NotImplemented("flxELoad::assembleForces_1");
      }
      const tdouble p1 = pos1->calc();
      const tdouble p2 = pos2->calc();
      if (p2<=p1 || p1<-1.0 || p2<-1.0 || p1>1.0 || p2>1.0) {
	throw FlxException("flxELoad::assembleForces_2","Problem with start and/or end of line load.");
      }
      flxDOFe* de = edge.find_dof(dofDir,false,true);
      const tuint PORDER = de->get_DOFpOrder();
      tuint i1,i2;
      const bool b1 = flxFEMrefine_1D::search_interval(i1,p1,meshInterval,meshInterval_size);
      const bool b2 = flxFEMrefine_1D::search_interval(i2,p2,meshInterval,meshInterval_size);
      if (b2) --i2;
      flxVec fv(PORDER*(i2-i1+1)+1);
      if (i1==i2) {
	integrate_help(edge, (b1?-1.0:(flxFEMrefine_1D::get_lx_sub(i1,p1,meshInterval))),(b2?1.0:(flxFEMrefine_1D::get_lx_sub(i2,p2,meshInterval))),i1,meshInterval,PORDER,fv.get_tmp_vptr());
      } else {
	integrate_help(edge, (b1?-1.0:(flxFEMrefine_1D::get_lx_sub(i1,p1,meshInterval))),1.0,i1,meshInterval,PORDER,&fv[0]);
	for (tuint i=i1+1;i<i2;++i) {
	  integrate_help(edge,-1.0,1.0,i,meshInterval,PORDER,&fv[PORDER*(i-i1)]);
	}
	integrate_help(edge,-1.0,(b2?1.0:(flxFEMrefine_1D::get_lx_sub(i2,p2,meshInterval))),i2,meshInterval,PORDER,&fv[PORDER*(i2-i1)]);
      }
      const tdouble fac = lc.get_factor()*edge.calc_length()/2.0;
      fv *= fac;    
      // assemble to global vector
	for (tuint i=i1;i<=i2;++i) {
	  edge.update_local_ForceVec(i,de,0,fv[PORDER*(i-i1)]);
	  if (PORDER>1) edge.update_local_ForceVec(i,de,2,&fv[PORDER*(i-i1)+1],PORDER-1);;
	}
	edge.update_local_ForceVec(i2,de,1,fv[PORDER*(i2-i1+1)]);
    } else {		// point load
      const tdouble lx = pos1->calc();
      edge.update_globalConst(lx);
      const tdouble F = value->get_fun()->calc()*lc.get_factor();
      tuint interval;
      if (flxFEMrefine_1D::search_interval(interval,lx,meshInterval,meshInterval_size)) {
	if (global) {
	  throw FlxException_NotImplemented("flxELoad::assembleForces_3");
	} else {
	  edge.update_local_ForceVec(interval,edge.find_dof(dofDir,false,true),0,F);
	}
      } else {
	if (global) {
	  throw FlxException_NotImplemented("flxELoad::assembleForces_4");
	} else {
	  flxDOFe* de = edge.find_dof(dofDir,false,true);
	  const tuint PORDER = de->get_DOFpOrder();
	  flxVec fv(PORDER+1);
	  hLP_const(fv,flxFEMrefine_1D::get_lx_sub(interval,lx,meshInterval),F);
	  edge.update_local_ForceVec(interval,de,0,fv[0]);
	  edge.update_local_ForceVec(interval,de,1,fv[1]);
	  if (PORDER>1) edge.update_local_ForceVec(interval,de,2,&fv[2],PORDER-1);
	}
      }
    }
  }
  
        
  if (next) next->assembleForces(edge,meshInterval,meshInterval_size);
}

void flxELoad::log_info(std::ostream& lout)
{
  lout << "lc=" << lc.get_index() << ", DOF=" << FlxDof::get_dof(dofDir) << " (";
  if (global) {
    lout << "global";
  } else {
    lout << "local";
  }
  lout << "), type=";
  if (pos2) {
    lout << "line-load";
  } else {
    lout << "point-load";
  }
  lout << ", pos=";
  if (pos2) {
    lout << "[" << pos1->write() << ";" << pos2->write() << "]";
  } else {
    lout << pos1->write();
  }
  lout << ", value=" << value->write();
}
