package fem;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.TimeZone;

import materials.Material;
import math.Maths;
import math.matrix.Matrix;
import math.matrix.Vector;
import awt.Draw;
import exception.IllegalDimensionException;
import exception.SingularException;

public class FachwerkLinear{
	private int	elements, nodes;
	
	/**
	 * unknown DOFs, positions (indices+1)
	 */
	private Vector ia;
	
	/**
	 * known DOFs, positions (indices+1)
	 */
	private Vector ib;
	private int[][] inc;
	private double balanceH, balanceV;
	private Vector L, A, E, alpha, Ua, Ub, U, Fa, Fb, F, f, sigma;
	private Matrix K;
	
	/**
	 * @param nodes
	 * @param bars
	 */
	public FachwerkLinear(int nodes, int bars){
		setNodes(nodes);
		setBars(bars);
	}
	
	/**
	 * Numbers of bars
	 * @param bars
	 */
	public void setBars(int bars){
		this.elements = bars;
	}
	
	/**
	 * Element lengths
	 * @param L lengths
	 */
	public void setL(double... L){
		this.L = new Vector(L);
	}
	
	/**
	 * Element areas
	 * @param A areas
	 */
	public void setA(double[] A){
		this.A = new Vector(A);
	}
	
	/**
	 * Element areas scalar multiplication
	 * @param A areas
	 * @param s scalar
	 */
	public void setA(double[] A, double s){
		this.A = new Vector(A).times(s);
	}
	
	/**
	 * Element elastics
	 * @param E
	 */
	public void setE(double E){
		this.E = Vector.fill(elements,E);
	}
	
	/**
	 * Element elastics
	 * @param E
	 */
	public void setE(double[] E){
		this.E = new Vector(E);
	}
	
	/**
	 * Element angles
	 * @param alpha angles
	 */
	public void setAlpha(double... alpha){
		this.alpha = new Vector(alpha);
	}
	
	/**
	 * Set incidence table.
	 * enter global notes for each bar
	 * @param inc incidence
	 */
	public void setInc(int[][] inc){
		int i;
		this.inc = new int[inc.length][4];
		for(i=0; i<inc.length; i++)
			this.inc[i] = new int[]{inc[i][0]*2-1, inc[i][0]*2, inc[i][1]*2-1, inc[i][1]*2};
//		System.out.println(Matrix.toString(this.inc));
	}
	
	/**
	 * Get incidence table
	 */
	public int[][] getInc(){
		return inc;
	}
	
	/**
	 * Get incidence value
	 */
	public int getInc(int i, int j){
		return inc[i][j];
	}
	
	/**
	 * @param nodes
	 */
	public void setNodes(int nodes){
		this.nodes = nodes;
	}
	
	/**
	 * DOFs of unknown displacements
	 * @param ia DOFs
	 */
	public void setIa(int... ia){
		if(ia.length == 0){
			int i, j, k;
			ia = new int[nodes*2-ib.length()];
			for(i=0, j=0, k=0; i<nodes*2; i++){
				// if dof is not a known displacement
				if((j < ib.length() && ib.get(j) != i+1) || j >= ib.length()){
					ia[k] = i+1; // save dof as unknown
					k++;
				} else {
					j++;
				}
			}
		}
		this.ia = new Vector(ia);
		this.Fa = new Vector(ia.length);
	}
	
	/**
	 * DOFs of known displacements
	 * @param ib DOFs
	 */
	public void setIb(int... ib){
		Arrays.sort(ib);
		this.ib = new Vector(ib);
		setIa();
		Ub = new Vector(ib.length);
	}
	
	void buildK(){
		K = fem.K.zx(inc, L, E, A, alpha);
	}
	
	public Matrix getK(){
		return K;
	}
	
	/**
	 * known load on global DOF
	 * @param dof
	 * @param fa load
	 */
	public void setFa(int dof, double fa){
		Fa.set(ia.indexOf(dof), fa);
	}
	
	/**
	 * known loads on global DOF DOFs
	 * @param Fa loads
	 */
	public void setFa(double[] Fa){
		this.Fa = new Vector(Fa);
	}
	
	/**
	 * known loads on global DOF DOFs
	 * @param Fa load
	 * @param s factor
	 */
	public void setFa(double[] Fa, double s){
		this.Fa = new Vector(Fa).times(s);
	}
	
	/**
	 * known displacements on global DOFs, in the order of ib.
	 * @param Ub displacements
	 */
	public void setUb(double... Ub){
		this.Ub = new Vector(Ub);
	}
	
	/**
	 * known displacement on global DOF
	 * @param dof
	 * @param ub displacement
	 */
	public void setUb(int dof, double ub){
		Ub.set(ib.indexOf(dof), ub);
	}
	
	public void calcU() throws IllegalDimensionException{
		U = new Vector(nodes*2);
		if(U.length()!=K.getM())
			throw new IllegalDimensionException("Elements are not allocated. Check table of incidences.");
		Matrix Kaa = K.get(ia.getInts(),ia.getInts(),true);
		Matrix Kab = K.get(ia.getInts(),ib.getInts(),true);
		try {
	        long time = -System.currentTimeMillis();
			Matrix KaaI = Kaa.inverse();
	        time += System.currentTimeMillis();
	        TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
	        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:SSS");
	        System.out.println(sdf.format(time));
			Ua = KaaI.times(Fa.minus(Kab.times(Ub)));
			U.fillPos(ia.getInts(), Ua);
		} catch (IllegalDimensionException | SingularException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public Vector getU(){
		return U;
	}
	
	/**
	 * Get vector value at position i.
	 * @param i
	 * @return value at position i
	 */
	public double getU(int i){
		return U.get(i);
	}
	
	/**
	 * Get vector U scalar multiplication.
	 * @param s scalar
	 * @return s U
	 */
	public Vector getU(double s){
		return U.times(s);
	}
	
	/**
	 * Print vector U
	 * @throws IllegalDimensionException 
	 */
	public void printU() throws IllegalDimensionException{
		printU(1);
	}
	
	public void printU(double s) throws IllegalDimensionException{
		if(U==null)calcU();
		System.out.printf("Verschiebungen U"+(s==1?"":" *%.0e")+":%n",s);
		U.times(s).println();
	}
	
	public void calcF(){
		F = new Vector(nodes*2);
		Matrix Kab = K.get(ia.getInts(),ib.getInts(),true);
		Matrix Kbb = K.get(ib.getInts(),ib.getInts(),true);
		F.fillPos(ia.getInts(), Fa);
		try {
			Fb = Kab.transpose().times(Ua).plus(Kbb.times(Ub));
			F.fillPos(ib.getInts(), Fb);
		} catch (IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public Vector getF(){
		return F;
	}
	
	public void printF(){
		printF(1);
	}
	
	public void printF(double s){
		if(F==null)calcF();
		System.out.printf("Lasten (äußere und Lager) F"+(s==1?"":" *%.0e")+":%n",s);
		F.times(s).println();
	}
	
	public void calcf() throws IllegalDimensionException{
		int i;
		if(elements>L.length() || elements>A.length() || elements>alpha.length() ||
				elements>inc.length)
			throw new IllegalDimensionException("Elements are not allocated.");
		f = new Vector(elements);
		for(i=0; i<elements; i++)
			try {
				f.set(i, fem.K.bar2(L.get(i), E.get(i)*A.get(i)).
												times(T.zx2(alpha.get(i))).
												times(U.sub(inc[i],true)).get(1));
			} catch (IllegalDimensionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
	
	public Vector getf(){
		return f;
	}
	
	public void printf() throws IllegalDimensionException{
		printf(1);
	}
	
	public void printf(double s) throws IllegalDimensionException{
		if(f==null)calcf();
		System.out.printf("Stabkräfte S"+(s==1?"":" *%.0e")+":%n",s);
		f.times(s).println();
	}
	
	public void solve() throws IllegalDimensionException{
        long time = -System.currentTimeMillis();
        long timed = -System.currentTimeMillis();
        TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:SSS");
		buildK();
        timed += System.currentTimeMillis();
		System.out.println("Builded K matrix. ("+sdf.format(timed)+")");
        timed = -System.currentTimeMillis();
		calcU();
        timed += System.currentTimeMillis();
		System.out.println("Calculated displacements. ("+sdf.format(timed)+")");
        timed = -System.currentTimeMillis();
		calcF();
        timed += System.currentTimeMillis();
		System.out.println("Calculated global loads. ("+sdf.format(timed)+")");
        timed = -System.currentTimeMillis();
		calcf();
        timed += System.currentTimeMillis();
		System.out.println("Calculated bar loads. ("+sdf.format(timed)+")");
        timed = -System.currentTimeMillis();
		calcSigma();
        timed += System.currentTimeMillis();
		System.out.println("Calculated stresses. ("+sdf.format(timed)+")");
        time += System.currentTimeMillis();
        System.out.println("Total time: "+sdf.format(time));
		System.out.println();
	}
	
	public void calcSigma(){
		sigma = f.overE(A);
	}
	
	public Vector getSigma(){
		return sigma;
	}
	
	public void printSigma(){
		printSigma(1);
	}
	
	public void printSigma(double s){
		if(sigma==null)calcSigma();
		System.out.printf("Stabspannungen sigma"+(s==1?"":" *%.0e")+":%n",s);
		sigma.times(s).println();
	}
	
	public void balance(){
		int i;
		balanceH = 0;
		balanceV = 0;
		for(i=0; i<nodes; i++){
			balanceH += F.get(2*i-1);
			balanceV += F.get(2*i);
		}
	}
	
	public void printBalance(){
		System.out.printf("Summe H: % .3e N%n",balanceH);
		System.out.printf("Summe V: % .3e N%n",balanceV);
	}
	
	public void printAll() throws IllegalDimensionException{
		printU();
		printF();
		printf();
		printSigma();
	}
	
	/**
	 * Print all with factor
	 * @param U factor, 1e+3 for mm with SI
	 * @param F factor, 1e-3 for kN with SI
	 * @param f factor, 1e-3 for kN with SI
	 * @param sigma factor, 1e-6 for MPa or N/mm² with SI
	 * @throws IllegalDimensionException 
	 */
	public void printAll(double U, double F, double f, double sigma) throws IllegalDimensionException{
		printU(U);
		printF(F);
		printf(f);
		printSigma(sigma);
		printBalance();
	}
	
	public double[][] dataPre(){
		Vector ub = new Vector(nodes*2);
		ub.fillPos(ib.getInts(), Ub);
//		ub.toConsole();
		double[][] nodevec = new double[elements][2];
		for(int i=0; i<elements; i++){
			nodevec[i] = new double[]{
				L.get(i)*Maths.cosd(alpha.get(i)),
				L.get(i)*Maths.sind(alpha.get(i))};
			nodevec[i][0] += ub.get(getInc(i, 2)-1) - ub.get(getInc(i, 0)-1);
			nodevec[i][1] += ub.get(getInc(i, 3)-1) - ub.get(getInc(i, 1)-1);
		}
//		new Matrix(nodevec).toConsole();
		Matrix incidences = new Matrix(getInc());
		double[][] coord = new double[elements][4];
		for(int i=0; i<elements; i++){
			if(getInc(i,0)==1){
				coord[i][0] = ub.get(0);
				coord[i][1] = ub.get(1);
				coord[i][2] = nodevec[i][0];
				coord[i][3] = nodevec[i][1];
			} else {
				int index = incidences.findInRow(getInc(i, 0), 2);
//				System.out.println(index);
				coord[i][0] = coord[index][2];
				coord[i][1] = coord[index][3];
				coord[i][2] = coord[index][2]+nodevec[i][0];
				coord[i][3] = coord[index][3]+nodevec[i][1];
			}
		}
//		new Matrix(coord).toConsole();
		return coord;
	}
	
	public double[][] dataDif(){
		double[][] du = new double[elements][4];
		for(int i=0; i<elements; i++){
			int nodel = getInc(i, 0);
			int noder = getInc(i, 2);
			du[i] = new double[]{getU(nodel-1),getU(nodel),
					getU(noder-1),getU(noder)};
		}
//		new Matrix(du).toConsole();
		return du;
	}
	
	public void plot(){
		Draw fwd = new Draw("Truss");
		fwd.plotLines(dataPre(),dataDif());
	}
	
	public void animate() throws IllegalDimensionException{
		if(U.length()==0) throw new IllegalDimensionException("No solution.");
		Draw fwd = new Draw("Truss");
		fwd.animateFEM(dataPre(),dataDif(),f.getArray());
	}
	
	/**
	 * Demo
	 * @param args
	 */
	public static void main(String[] args){
		FachwerkLinear fw;
//		{
//			fw = new FachwerkLinear(6,9);
//			// PREPROCESSING
//			fw.setL(5,3,5,Math.sqrt(34),3,Math.sqrt(18),3,Math.sqrt(34),3);
//			fw.setA(new double[]{
//					2.5,2.5,2.5,3.5,2.5,3.5,2.5,3.5,2.5},1e-4);
//			fw.setE(Material.STEEL.getYoungsModulus().getValue());
//			fw.setAlpha(0,0,0,180-Maths.atand(3./5),90,45,90,Maths.atand(3./5),0);
//			// ANALYZING
//			// TODO: Geometrische verknüpfen, bzw. globale Knoten geometrisch erstellen und
//			// dann mit dieser Tabelle die Stäbe verknüpfen.
//			fw.setInc(new int[][]{
//					{1,2},{2,3},{3,4},{4,5},{3,5},{2,5},{2,6},{1,6},{6,5}});
//			fw.setIb(2,3,4,8);
//			
//			fw.setFa(10,-50e3);
//			fw.setFa(11, 20e3);
//			fw.setFa(12,-50e3);
//			// rechtes Lager absenken:
////			fw.setUb(8,-0.01);
////			fw.setUb(8,-0.1);
////			fw.setUb(8,-0.25);
////			fw.setUb(8, -.01);
//			// SOLVING and POSTPROCESSING
//			try {
//				fw.solve();
//				fw.printAll(1e3,1e-3,1e-3,1e-6);
//				// PLOTING / ANIMATION
////				fw.plot();
//				fw.animate();
//			} catch (IllegalDimensionException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
//		}
		{
			fw = new FachwerkLinear(7,11);
			// PREPROCESSING
			fw.setL(5,3,5,Math.sqrt(34),3,Math.sqrt(18),3,Math.sqrt(34),3,3,Math.sqrt(18));
			fw.setA(new double[]{
					2.5,2.5,2.5,3.5,2.5,3.5,2.5,3.5,2.5,2.5,2.5},1e-4);
			fw.setE(Material.STEEL.getYoungsModulus().getValue());
			fw.setAlpha(0,0,0,180-Maths.atand(3./5),90,45,90,Maths.atand(3./5),0,90,-45);
			// ANALYZING
			fw.setInc(new int[][]{
					{1,2},{2,3},{3,4},{4,5},{3,5},{2,5},{2,6},{1,6},{6,5},{6,7},{7,5}});
			fw.setIb(2,3,4,8);
			fw.setFa(10,-50e3);
			fw.setFa(11, 20e3);
			fw.setFa(14,-50e3);
			// SOLVING and POSTPROCESSING
			try {
				fw.solve();
				fw.printAll(1e3,1e-3,1e-3,1e-6);
				// PLOTING / ANIMATION
//				fw.plot();
				fw.animate();
			} catch (IllegalDimensionException e) {
				e.printStackTrace();
			}
		}
	}
}