package hinst.pjbench;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import android.os.Bundle;
import android.app.Activity;
import android.content.res.AssetManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends Activity {
	
	protected native void SetPackagePath(String filePath);
	protected native void Test();
	
	public final String packageName = MainActivity.class.getPackage().getName();
	public final String tag = "Java";	
	public final double nanoSecondsToSeconds = (double)1 / (double)1000000000;
	protected final double nsts = nanoSecondsToSeconds;
	protected final String logFilePath = "/mnt/sdcard/log.java.txt";
	protected BufferedWriter logFile = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		try {
			super.onCreate(savedInstanceState);
			logFile = new BufferedWriter(new FileWriter(logFilePath));
			setContentView(R.layout.activity_main);
			System.loadLibrary("PJBench");
			setPackagePath();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	protected void WriteLog(String s) {
		Log.i(tag, s);
		try {
			logFile.write(s + "\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	protected long getNanoTime() {
		return System.nanoTime();
	}
	
	protected void setPackagePath() {
		try {
			String appPackagePath = 
					getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
			SetPackagePath(appPackagePath);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		boolean result = true;
		if (false) 
			/**/;
		else if (id == R.id.action_test_java)
			safeJavaTest();
		else if (id == R.id.action_test_fpc)
			Test();
		else if (id == R.id.action_exit)
			finish();
		else
			result = super.onOptionsItemSelected(item);
		return result;
	}
	
	protected void safeJavaTest() {
		try {
			javaTest();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	protected Document loadTestDocument() throws Exception {
		long time = getNanoTime();
		AssetManager assetManager = getAssets();
		InputStream input = assetManager.open("data.xml");
		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		Document doc = builder.parse(input);
		time = System.nanoTime() - time;
		WriteLog("XML Document loaded; time spent: " + ((double)time * nsts) + " seconds");
		WriteLog("Matrices in list: " + doc.getFirstChild().getChildNodes().getLength() + " items");
		return doc;
	}
	
	protected int[][] loadMatrix(Node node) {
		int width = Integer.parseInt(node.getAttributes().getNamedItem("width").getTextContent());
		int height = Integer.parseInt(node.getAttributes().getNamedItem("width").getTextContent());
		int[][] matrix = new int[width][height];
		Node column = node.getFirstChild();
		int x = 0;
		while (column != null) {
			if (column.getTextContent().trim().length() > 0) {
				Node cell = column.getFirstChild();
				int y = 0;
				while (cell != null) {
					if (cell.getTextContent().trim().length() > 0) {
						matrix[x][y] = Integer.parseInt(cell.getTextContent());
						y++;
					}
					cell = cell.getNextSibling();
				}
				++x;
			}
			column = column.getNextSibling();
		}
		return matrix;
	}
	
	protected int[][][] loadMatrixArray(Document doc) throws Exception {
		long time = getNanoTime();
		Node node = doc.getFirstChild().getFirstChild();
		List<int[][]> matrixList = new LinkedList<int[][]>(); 
		while (node != null) {
			if (node.getTextContent().trim().length() > 0) {
				int[][] matrix = loadMatrix(node);
				matrixList.add(matrix);
			}
			node = node.getNextSibling();
		}
		int[][][] result =  matrixList.toArray(new int[0][][]);
		time = System.nanoTime() - time;
		WriteLog("Load matrix array from xml: time spent: " + (nsts * time) + " secs");
		WriteLog("Items in array: " + result.length);
		return result;
	}
	
	// calc product of square matrices
	protected int[][] prodSM(int[][] a, int[][] b) {
		int w = a.length;
		int[][] c = new int[w][w];
		for (int x = 0; x < w; ++x) {
			for (int y = 0; y < w; ++y) {
				int cellValue = 0;
				for (int i = 0; i < w; ++i)
					cellValue = cellValue + a[x][i] * b[i][y];
				c[x][y] = cellValue;
			}
		}
		return c;
	}
	
	// !
	protected int[][][] bench(int[][][] matrixArray) {
		long time = getNanoTime();
		int n = matrixArray.length;
		int[][][] resultArray = new int[n][][];
		for (int cycle = 0; cycle < 10; ++cycle)
			for (int i = 0; i < n; ++i)
				resultArray[i] = prodSM(matrixArray[i], matrixArray[n - i - 1]);
		time = getNanoTime() - time;
		WriteLog("matrix products calculated; time spent: " + (nsts * time) + " secs");
		return resultArray;
	}
	
	protected Document matrixArrayToDocument(int[][][] array) throws Exception {
		long time = getNanoTime();
		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		Document doc = builder.newDocument();
		Node matrixListNode = doc.createElement("matrixList");
		for (int i = 0; i < array.length; ++i) {
			Node matrixNode = doc.createElement("matrix");
			Node widthAttr = doc.createAttribute("width");
			widthAttr.setTextContent("" + array[i].length);
			matrixNode.getAttributes().setNamedItem(widthAttr);
			Node heightAttr = doc.createAttribute("height");
			heightAttr.setTextContent("" + array[i].length);
			matrixNode.getAttributes().setNamedItem(heightAttr);
			for (int x = 0; x < array[i].length; ++x) {
				Node column = doc.createElement("column");
				for (int y = 0; y < array[i].length; ++y) {
					Node cell = doc.createElement("cell");
					cell.setTextContent("" + array[i][x][y]);
					column.appendChild(cell);
				}
				matrixNode.appendChild(column);
			}
			matrixListNode.appendChild(matrixNode);
		}
		doc.appendChild(matrixListNode);	
		time = getNanoTime() - time;
		WriteLog("Save matrix array to xml document: " + (nsts * time) + " seconds");
		return doc;
	}
	
	protected void saveDocumentToFile(Document doc, String filePath) throws Exception {
		long time = getNanoTime();
		Transformer transformer = TransformerFactory.newInstance().newTransformer();
		StreamResult output = new StreamResult(new File(filePath));
		Source input = new DOMSource(doc);
		transformer.transform(input, output);
		time = getNanoTime() - time;
		WriteLog("Save xml document to file: " + (nsts * time) + " seconds");
	}
	
	protected void save(int[][][] array, String filePath) throws Exception {
		Document doc = matrixArrayToDocument(array);
		saveDocumentToFile(doc, filePath);
	}
	
	protected void emptyCycleBench() {
		long time = getNanoTime();
		for (int i = 0; i < 100000000; i++) 
			;
		time = getNanoTime() - time;
		WriteLog("empty cycle; time spent: " + (nsts * time) + " secs");
	}
	
	protected void javaTest() throws Exception {
		Document doc = loadTestDocument();
		int[][][] matrixArray = loadMatrixArray(doc);
		doc = null;
		int[][][] resultMatrixArray = bench(matrixArray);
		emptyCycleBench();
		save(resultMatrixArray, "/mnt/sdcard/r.java.xml");
	}

	@Override
	protected void onDestroy() {
		try {
			logFile.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		super.onDestroy();
	}
	
}
