package org.bdware.sc.trace;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.bdware.sc.engine.DesktopEngine;

import jdk.internal.dynalink.CallSiteDescriptor;
import org.bdware.sc.engine.SyncMechUtil;
import wrp.jdk.nashorn.internal.objects.NativeArray;
import wrp.jdk.nashorn.internal.runtime.ConsString;
import wrp.jdk.nashorn.internal.runtime.Context;
import wrp.jdk.nashorn.internal.runtime.JSType;
import wrp.jdk.nashorn.internal.runtime.PropertyMap;
import wrp.jdk.nashorn.internal.runtime.ScriptObject;
import wrp.jdk.nashorn.internal.runtime.TraceSetBehavior;
import wrp.jdk.nashorn.internal.runtime.TraceSetupArray;
import wrp.jdk.nashorn.internal.runtime.TraceSetupScriptObject;
import wrp.jdk.nashorn.internal.runtime.arrays.ArrayData;

public class TraceRecordUtil {
	SyncMechUtil syncUtil;
	DesktopEngine engine;

	public ArrayList<TraceRecord> traceRecords;

	public TraceRecord currentTraceRecord;   //当前事务中所有非TraceSet的trace
	public Map<Integer,TraceInitArray> currentArrayMap;
	public Map<TraceSetIdentifier,TraceSet> currentMap;   //当前事务中所有TraceSet的精简记录

	public TraceRecordUtil(DesktopEngine de,SyncMechUtil sync) {
		this.engine = de;
		this.syncUtil = sync;
	}

	public ArrayList<TraceRecord> getTraceRecords() {
		if (traceRecords == null) {
			return null;
		}
		return this.traceRecords;
	}

	public TraceRecord getTraceRecord(int c) {
		if (traceRecords == null)
			return null;
		else if (traceRecords.size() <= c)
			return null;
		else
			return traceRecords.get(c);
	}

	public String getTraceRecordsContent() {
		StringBuilder str = new StringBuilder();

		for (int i = 0; i < traceRecords.size(); i++) {
			str.append("No." + i + "record : \n" + traceRecords.get(i).toString());
		}

		return str.toString();
	}

	public static String getTraceRecordsByFile(String fileName) {
		ArrayList<TraceRecord> traceRecords = null;
		File file = new File(fileName);
		ObjectInputStream reader;
		try {
			FileInputStream fileout = new FileInputStream(file);
			GZIPInputStream gzin = new GZIPInputStream(fileout);
			reader = new ObjectInputStream(gzin);
			traceRecords = (ArrayList<TraceRecord>) reader.readObject();
			reader.close();
		} catch (IOException | ClassNotFoundException e) {
			e.printStackTrace();
		}

		StringBuilder str = new StringBuilder();
		for (int i = 0; i < traceRecords.size(); i++) {
			str.append("No." + i + "record : \n" + traceRecords.get(i).toString());
		}
		return str.toString();
	}

	//添加简化过的TraceSet
	public void addSmpSet() {
		currentTraceRecord.traces.addAll(currentArrayMap.values());
		currentTraceRecord.traces.addAll(currentMap.values());
	}

	public void saveTraceRecords(String fileName) {
		if (traceRecords == null) {
			System.out.println("[saveTraceRecords] traceRecords is null,can't save traceRecords to fill!");
		}

		File traceFile = new File(fileName + ".trace"); // trace文件名中不带时间
		File parent = traceFile.getParentFile();
		if (!parent.exists())
			parent.mkdirs();

		ObjectOutputStream writer;
		try {
			FileOutputStream fileout = new FileOutputStream(traceFile);
			GZIPOutputStream out = new GZIPOutputStream(fileout);
			writer = new ObjectOutputStream(out);
			writer.writeObject(traceRecords);
			writer.flush();
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void startRecordTrace() {
		this.traceRecords = new ArrayList<TraceRecord>();
//		recording = true;

		//set behavior
		Context.TRACESETBEHAVIOR = new TraceSetBehavior() {

			@Override
			public void trace(CallSiteDescriptor desc, Object... args) {
				int id = ((ScriptObject) args[0]).getObjectID();
				Object key = null;
				Object value = null;

				if (args.length == 3) {
					key = JSType.toPrimitive(args[1]);
					value = args[2];
				} else if (args.length == 2) {
					key = desc.getName().split(":")[2];
					value = args[1];
				}

				if (value instanceof Integer || value instanceof String || value instanceof Double)
					;
				else if (value instanceof ConsString)
					value = value.toString();
				else
					value = produceJS(value);

				currentMap.put(new TraceSetIdentifier(id,key),new TraceSet(id,key,value));
			}
		};

		//setup script object
		Context.TRACESETUPSCRIPTOBJECT = new TraceSetupScriptObject() {

			@Override
			public void trace(ScriptObject arg, PropertyMap map) {
				/*
				 * TraceSetup tracesetup = new TraceSetup((JSScript)produceJS(arg));
				 * traceRecord.record(tracesetup);
				 */

				TraceSetup tracesetup = new TraceSetup((JSScript) produceJS(arg));

				/*
				 * System.out.println("[startRecord] setupScriptObject argID=" +
				 * arg.getObjectID() + " map=" + map.size()); Iterator<String> keys =
				 * arg.propertyIterator(); while(keys.hasNext()) { String key = keys.next();
				 * System.out.println(key + " ; " +
				 * map.findProperty(key).getClass().getCanonicalName());
				 * tracesetup.add(map.findProperty(key)); }
				 */

				currentTraceRecord.record(tracesetup);
			}
		};

		//setup array
		Context.TRACESETUPARRAY = new TraceSetupArray() {
			@Override
			public void trace(ArrayData arg, int id) {
				TraceInitArray traceinitarray = new TraceInitArray(id);
				for (int i = 0; i < arg.length(); i++) {
					Object obj = arg.getObject(i);

					if (obj instanceof Integer || obj instanceof String || obj instanceof Double)
						;
					else if (obj instanceof ConsString)
						obj = obj.toString();
					else
						obj = produceJS(obj);

					traceinitarray.put(i, obj);
				}
				currentArrayMap.put(traceinitarray.getArrayId(), traceinitarray);
			}
		};

		/*
		 * Context.TRACESETGLOBALOBJECTPROTO = new TraceSetGlobalObjectProto() {
		 *
		 * @Override public void trace(int id, int id2,List<Object> keys,List<Object>
		 * values) { System.out.println("[startRecord] set global object proto id=" + id
		 * + " id2=" + id2 + " size=" + keys.size());
		 *
		 *
		 *
		 * TraceInitObject traceinitobject = new TraceInitObject(id,id2); for(int i =
		 * 0;i < keys.size();i++) { System.out.println("i=" + i + " key=" + keys.get(i)
		 * + " value=" + values.get(i)); } currentTraceRecord.record(traceinitobject); }
		 *
		 * };
		 */
	}

	public void stopRecordTrace() {
		Context.TRACESETBEHAVIOR = null;
		Context.TRACESETUPARRAY = null;
		Context.TRACESETUPSCRIPTOBJECT = null;
//		recording = false;
	}

	public static JS produceJS(Object arg) {
		if (arg == null) {
			return new JSNull();
		} else if (arg instanceof NativeArray) {
			JSArray arr = new JSArray(((ScriptObject) arg).getObjectID());
			return arr;
		} else if (arg instanceof ScriptObject) {
			JSObject obj = new JSObject(((ScriptObject) arg).getObjectID());
			return obj;
		} else if (arg instanceof wrp.jdk.nashorn.internal.runtime.Undefined) {
			return new JSUndifined();
		} else {
			System.out.println("[produceJS] arg encounter new type!" + arg.toString() + " " + arg.getClass());
			return new JS();
		}
	}

	//每次事务开始时初始化
	public void startNext() {
		currentTraceRecord = new TraceRecord();
		currentArrayMap = new LinkedHashMap<Integer,TraceInitArray>();
		currentMap = new LinkedHashMap<TraceSetIdentifier,TraceSet>();
	}

	//每次事务结束时记录
	public void eachFinish() {
		addSmpSet();
		currentTraceRecord.record(new TraceDone(ScriptObject.getAllocID()));
		traceRecords.add(currentTraceRecord);
	}
}