package jp.mydns.masahase.operateXML;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Calendar;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import jp.mydns.masahase.AbaqusResult.AbaqusResultType;
import jp.mydns.masahase.AbaqusResult.ElementIntegPointRecordType;
import jp.mydns.masahase.AbaqusResult.ElementRecordType;
import jp.mydns.masahase.AbaqusResult.ElementCentroidRecordType;
import jp.mydns.masahase.AbaqusResult.ElementSetType;
import jp.mydns.masahase.AbaqusResult.ElementType;
import jp.mydns.masahase.AbaqusResult.IncrementType;
import jp.mydns.masahase.AbaqusResult.NodeOutputType;
import jp.mydns.masahase.AbaqusResult.NodeRefType;
import jp.mydns.masahase.AbaqusResult.NodeSetType;
import jp.mydns.masahase.AbaqusResult.NodeType;
import jp.mydns.masahase.AbaqusResult.ObjectFactory;
import jp.mydns.masahase.AbaqusResult.OutputValueType;
import jp.mydns.masahase.util.Cache;
import jp.mydns.masahase.util.CacheSource;

/**
 * {@link jp.mydns.masahase.AbaqusResult}で定義・作成されるXML操作
 * 
 * @author MASA.H
 * 
 */
public class AbaqusResult {
	InputStream input;
	OutputStream output;
	ObjectFactory objfct;
	JAXBContext jaxbcont;
	AbaqusResultType rootnode;
	final static int cache_size = 100;
	private Cache<Long, ElementType> el_cache;
	private Cache<Long, NodeType> nd_cache;

	/**
	 * デフォルトコンストラクタ
	 * 
	 * @throws JAXBException
	 */
	public AbaqusResult() throws JAXBException {
		objfct = new ObjectFactory();
		jaxbcont = JAXBContext.newInstance("jp.mydns.masahase.AbaqusResult");
		input = null;
		output = null;
		rootnode = null;
		el_cache = new Cache<Long, ElementType>(cache_size);
		nd_cache = new Cache<Long, NodeType>(cache_size);
	}

	/**
	 * XMLを読む
	 * 
	 * @return 成功したか否か
	 */
	@SuppressWarnings("unchecked")
	public boolean readXML() {
		boolean ret;
		if (input != null) {
			try {
				Unmarshaller um = jaxbcont.createUnmarshaller();
				rootnode = (AbaqusResultType) ((JAXBElement<AbaqusResultType>) um
						.unmarshal(input)).getValue();
				ret = true;
				el_cache = new Cache<Long, ElementType>(cache_size,
						new CacheSource<Long, ElementType>() {
							public ElementType get(Long key) {
								ElementType ret = null;
								for (ElementType et : rootnode.getElement()) {
									if (et.getId() == key.longValue()) {
										ret = et;
										break;
									}
								}
								return ret;
							}
						});
				nd_cache = new Cache<Long, NodeType>(cache_size,
						new CacheSource<Long, NodeType>() {
							public NodeType get(Long key) {
								NodeType ret = null;
								for (NodeType nd : rootnode.getNode()) {
									if (nd.getId() == key.longValue()) {
										ret = nd;
										break;
									}
								}
								return ret;
							}
						});
			} catch (JAXBException e) {
                e.printStackTrace();
				ret = false;
			}
		} else {
			ret = false;
		}
		return ret;
	}

	/**
	 * XMLを書き出す
	 * 
	 * @return 成功したか否か
	 */
	public boolean writeXML() {
		boolean ret;
		if (output != null) {
			try {
				Marshaller ms = jaxbcont.createMarshaller();
				ms.marshal(rootnode, output);
				ret = true;
			} catch (JAXBException e) {
				ret = false;
			}
		} else {
			ret = false;
		}
		return ret;
	}

	/**
	 * 入力を設定
	 * 
	 * @param is
	 *            入力用のストリーム
	 */
	public void setInput(InputStream is) {
		input = is;
	}

	/**
	 * 出力を設定
	 * 
	 * @param os
	 *            出力用のストリーム
	 */
	public void setOutput(OutputStream os) {
		output = os;
	}

	/**
	 * IDから節点を取り出す
	 * 
	 * @param id
	 *            節点番号
	 * @return 節点
	 */
	public NodeType getNode(long id) {
		return nd_cache.get(Long.valueOf(id));
	}

	/**
	 * IDから要素を取り出す
	 * 
	 * @param id
	 *            要素番号
	 * @return 要素
	 */
	public ElementType getElement(long id) {
		return el_cache.get(Long.valueOf(id));
	}

	/**
	 * 節点の追加
	 * 
	 * @param id
	 *            節点番号
	 * @param x
	 *            座標1
	 * @param y
	 *            座標2
	 * @param z
	 *            座標3
	 * @return 新しい節点かどうか
	 */
	public boolean addNode(long id, double x, double y, double z) {
		boolean ret = false;
		NodeType nd = null;
		nd = objfct.createNodeType();
		nd.setId(id);
		nd.setC1(x);
		nd.setC2(y);
		nd.setC3(z);
		if (!rootnode.getNode().contains(nd)) {
			ret = true;
			rootnode.getNode().add(nd);
		}
		return ret;
	}

	/**
	 * ルートノードの作成
	 * 
	 * @param ver
	 *            バージョン
	 * @param date
	 *            日付
	 * @param time
	 *            時間
	 * @param length
	 *            代表長さ
	 * @param Title
	 *            タイトル
	 * @return 作成できたかどうか
	 */
	public boolean createRootNode(String ver, Calendar date, String time,
			double length, String Title) {
		boolean ret = false;
		if (rootnode == null) {
			try {
				rootnode = objfct.createAbaqusResultType();
				rootnode.setTitle(Title);
				rootnode.setVersion(ver);
				rootnode.setTypicalLength(length);
				XMLGregorianCalendar gdate = DatatypeFactory.newInstance()
						.newXMLGregorianCalendar();
				gdate.setDay(date.get(Calendar.DATE));
				gdate.setMonth(date.get(Calendar.MONTH));
				gdate.setYear(date.get(Calendar.YEAR));
				gdate.setHour(DatatypeConstants.FIELD_UNDEFINED);
				gdate.setMinute(DatatypeConstants.FIELD_UNDEFINED);
				gdate.setSecond(DatatypeConstants.FIELD_UNDEFINED);
				rootnode.setDate(gdate);
				String[] tmp = time.split(":");
				gdate.setDay(DatatypeConstants.FIELD_UNDEFINED);
				gdate.setMonth(DatatypeConstants.FIELD_UNDEFINED);
				gdate.setYear(DatatypeConstants.FIELD_UNDEFINED);
				gdate.setHour(Integer.parseInt(tmp[0]));
				gdate.setMinute(Integer.parseInt(tmp[1]));
				gdate.setSecond(Integer.parseInt(tmp[2]));
				rootnode.setTime(gdate);
				ret = true;
			} catch (NumberFormatException e) {
				// TODO 自動生成された catch ブロック
				e.printStackTrace();
			} catch (DatatypeConfigurationException e) {
				// TODO 自動生成された catch ブロック
				e.printStackTrace();
			}
		}
		return ret;
	}

	/**
	 * 要素の追加
	 * 
	 * @param id
	 *            要素番号
	 * @param type
	 *            要素タイプ
	 * @param nodes
	 *            この要素に属する節点番号
	 * @return 追加できたかどうか
	 */
	public boolean addElement(long id, String type, long[] nodes) {
		boolean ret = false;
		ElementType et = objfct.createElementType();
		et.setId(id);
		et.setType(type);
		for (long l : nodes) {
			NodeRefType nr = objfct.createNodeRefType();
			nr.setNodeId(l);
			et.getNodeRef().add(nr);
		}
		if (!rootnode.getElement().contains(et)) {
			rootnode.getElement().add(et);
			ret = true;
		}
		return ret;
	}

	/**
	 * 節点出力の取得
	 * 
	 * @param NodeId
	 *            節点番号
	 * @param step
	 *            ステップ番号
	 * @param increment
	 *            インクリメント番号
	 * @param outputVariable
	 *            出力変数名
	 * @return 出力変数に応じた出力を保持した配列
	 */
	public Object[] getNodeOutput(long NodeId, long step, long increment,
			String outputVariable) {
		Object[] ret = null;
		IncrementType inc = null;
		for (IncrementType tmp : rootnode.getIncrement()) {
			if (tmp.getStepId() == step && tmp.getIncId() == increment) {
				inc = tmp;
				break;
			}
		}
		if (inc != null) {
			for (NodeOutputType tmp : inc.getNodeOutput()) {
				if (tmp.getNodeId() == NodeId
						&& tmp.getOutputVariable() == outputVariable) {
					ret = tmp.getFloatOrIntegerOrString().toArray();
				}
			}
		}

		return ret;
	}

	/**
	 * 節点出力の取得. ステップの最終インクリメントでの値を取得
	 * 
	 * @param NodeId
	 *            節点番号
	 * @param step
	 *            ステップ番号
	 * @param outputVariable
	 *            出力変数名
	 * @return 出力変数に応じた出力を保持した配列
	 */
	public Object[] getNodeOutput(long NodeId, long step, String outputVariable) {
		long inc = 0;
		for (IncrementType tmp : rootnode.getIncrement()) {
			if (tmp.getStepId() == step && inc < tmp.getIncId()) {
				inc = tmp.getIncId();
			}
		}
		return getNodeOutput(NodeId, step, inc, outputVariable);
	}

	/**
	 * 節点出力の取得. その出力変数で出力されている最終インクリメントの値を取得
	 * 
	 * @param NodeId
	 *            節点番号
	 * @param outputVariable
	 *            出力変数名
	 * @return 出力変数に応じた出力を保持した配列
	 */
	public Object[] getNodeOutput(long NodeId, String outputVariable) {
		Object[] ret = null;
		long buf = 0;
		for (IncrementType tmp : rootnode.getIncrement()) {
			if (buf != tmp.getStepId()) {
				ret = getNodeOutput(NodeId, tmp.getStepId(), outputVariable) == null ? ret
						: getNodeOutput(NodeId, tmp.getStepId(), outputVariable);
				buf = tmp.getStepId();
			}
		}
		return ret;
	}

	/**
	 * 要素集合名から要素番号を取得する
	 * 
	 * @param Name
	 *            要素集合名
	 * @return 要素番号の入った配列
	 */
	public long[] getElements(String Name) {
		long[] ret = new long[0];
        try{
        if(rootnode.getElementSet()!=null)
		for (ElementSetType est : rootnode.getElementSet()) {
			if (est.getName().equalsIgnoreCase(Name)) {
				ret = new long[est.getElementRef().size()];
				for (int i = 0; i < est.getElementRef().size(); i++) {
					ret[i] = est.getElementRef().get(i).getElementId();
				}
				break;
			}
		}
        }catch(java.lang.NullPointerException e){
        }
		return ret;
	}

	public long[] getElements() {
        assert rootnode!=null;
		long[] ret = new long[rootnode.getElements().intValue()];
		for (int i = 0; i < rootnode.getElements().intValue(); i++) {
			ret[i] = rootnode.getElement().get(i).getId();
		}
		return ret;
	}

	/**
	 * 節点集合名から節点番号を取得する
	 * 
	 * @param Name
	 *            節点集合名
	 * @return 節点番号の入った配列
	 */
	public long[] getNodes(String Name) {
		long[] ret = new long[0];
		for (NodeSetType nst : rootnode.getNodeSet()) {
			if (nst.getName() == Name) {
				ret = new long[nst.getNodeRef().size()];
				for (int i = 0; i < nst.getNodeRef().size(); i++) {
					ret[i] = nst.getNodeRef().get(i).getNodeId();
				}
				break;
			}
		}
		return ret;
	}

	/**
	 * 要素出力の取得
	 * 
	 * @param elemId
	 *            要素番号
	 * @param integ
	 *            積分点番号
	 * @param step
	 *            ステップ
	 * @param inc
	 *            インクリメント
	 * @param outputVariable
	 *            出力変数
	 * @return 要素出力
	 */
	public Object[] getElementOutput_integ(long elemId, long integ, long step,
			long inc, String outputVariable) {
		Object[] ret = null;
		for (IncrementType inct : rootnode.getIncrement()) {
			if (inct.getIncId() == inc && inct.getStepId() == step) {
				for (JAXBElement<? extends ElementRecordType> Eert : inct
						.getElementRecord()) {
					if (Eert.getValue().getLocate().equalsIgnoreCase(
							"integ_point")) {
						ElementIntegPointRecordType eiprt = (ElementIntegPointRecordType) Eert
								.getValue();
						if (eiprt.getElementId() == elemId
								&& eiprt.getIntegPointId() == integ) {
							for (OutputValueType ovt : eiprt.getOutputValue()) {
								if (ovt.getOutputVariable().equalsIgnoreCase(
										outputVariable)) {
									ret = ovt.getFloatOrIntegerOrString()
											.toArray();
								}
							}
						}
					}
				}
			}
		}
		return ret;
	}

	/**
	 * 要素出力の取得. そのステップでの最終出力を取得
	 * 
	 * @param elemId
	 *            要素番号
	 * @param integ
	 *            積分点番号
	 * @param step
	 *            ステップ番号
	 * @param outputVariable
	 *            出力変数
	 * @return 要素出力
	 */
	public Object[] getElementOutput_integ(long elemId, long integ, long step,
			String outputVariable) {
		long inc = 0;
		for (IncrementType tmp : rootnode.getIncrement()) {
			if (tmp.getStepId() == step && inc < tmp.getIncId()) {
				inc = tmp.getIncId();
			}
		}
		return getElementOutput_integ(elemId, integ, step, inc, outputVariable);
	}

	/**
	 * 要素出力の取得.最終出力を取得
	 * 
	 * @param ElemId
	 *            要素番号
	 * @param integ
	 *            積分点番号
	 * @param outputVariable
	 *            出力変数
	 * @return 要素出力
	 */
	public Object[] getElementOutput_integ(long ElemId, long integ,
			String outputVariable) {
		Object[] ret = null;
		long buf = 0;
		for (IncrementType tmp : rootnode.getIncrement()) {
			if (buf != tmp.getStepId()) {
				if (getElementOutput_integ(ElemId, integ, tmp.getStepId(),
						outputVariable) != null) {
					ret = getElementOutput_integ(ElemId, integ,
							tmp.getStepId(), outputVariable);
				}
				buf = tmp.getStepId();
			}
		}
		return ret;
	}

	/**
	 * 要素出力の取得
	 * 
	 * @param elemId
	 *            要素番号
	 * @param step
	 *            ステップ
	 * @param inc
	 *            インクリメント
	 * @param outputVariable
	 *            出力変数
	 * @return 要素出力
	 */
	public Object[] getElementOutput(long elemId, long step, long inc,
			String outputVariable) {
		Object[] ret = null;
		for (IncrementType inct : rootnode.getIncrement()) {
			if (inct.getIncId() == inc && inct.getStepId() == step) {
				for (JAXBElement<? extends ElementRecordType> Eert : inct
						.getElementRecord()) {
					if (Eert.getValue().getLocate().equalsIgnoreCase("element")) {
						ElementCentroidRecordType eiprt = (ElementCentroidRecordType) Eert
								.getValue();
						if (eiprt.getElementId() == elemId) {
							for (OutputValueType ovt : eiprt.getOutputValue()) {
								if (ovt.getOutputVariable().equalsIgnoreCase(
										outputVariable)) {
									ret = ovt.getFloatOrIntegerOrString()
											.toArray();
								}
							}
						}
					}
				}
			}
		}
		return ret;
	}

	/**
	 * 要素出力の取得. そのステップでの最終出力を取得
	 * 
	 * @param elemId
	 *            要素番号
	 * @param step
	 *            ステップ番号
	 * @param outputVariable
	 *            出力変数
	 * @return 要素出力
	 */
	public Object[] getElementOutput(long elemId, long step,
			String outputVariable) {
		long inc = 0;
		for (IncrementType tmp : rootnode.getIncrement()) {
			if (tmp.getStepId() == step && inc < tmp.getIncId()) {
				inc = tmp.getIncId();
			}
		}
		return getElementOutput(elemId, step, inc, outputVariable);
	}

	/**
	 * 要素出力の取得.最終出力を取得
	 * 
	 * @param ElemId
	 *            要素番号
	 * @param outputVariable
	 *            出力変数
	 * @return 要素出力
	 */
	public Object[] getElementOutput(long ElemId, String outputVariable) {
		Object[] ret = null;
		long buf = 0;
		for (IncrementType tmp : rootnode.getIncrement()) {
			if (buf != tmp.getStepId()) {
				if (getElementOutput(ElemId, tmp.getStepId(), outputVariable) != null) {
					ret = getElementOutput(ElemId, tmp.getStepId(),
							outputVariable);
				}
				buf = tmp.getStepId();
			}
		}
		return ret;
	}

}
