/**********************************************************************
* This file is part of iDempiere ERP Open Source                      *
* http://www.idempiere.org                                            *
*                                                                     *
* Copyright (C) Contributors                                          *
*                                                                     *
* This program 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 2      *
* of the License, or (at your option) any later version.              *
*                                                                     *
* This program 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 this program; if not, write to the Free Software         *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,          *
* MA 02110-1301, USA.                                                 *
*                                                                     *
* Contributors:                                                       *
* - Trek Global Corporation                                           *
* - Heng Sin Low                                                      *
**********************************************************************/
package com.trekglobal.idempiere.rest.api.json;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;

import org.adempiere.exceptions.AdempiereException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;
import org.compiere.model.GridField;
import org.compiere.model.GridFieldVO;
import org.compiere.model.MPInstance;
import org.compiere.model.MPInstancePara;
import org.compiere.model.MProcess;
import org.compiere.model.MProcessPara;
import org.compiere.model.MTable;
import org.compiere.print.MPrintFormat;
import org.compiere.process.ProcessInfo;
import org.compiere.process.ProcessInfoLog;
import org.compiere.process.ProcessInfoUtil;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.DisplayType;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Util;
import org.idempiere.distributed.IClusterService;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonReader;
import com.trekglobal.idempiere.rest.api.util.ClusterUtil;

/**
 * 
 * @author hengsin
 *
 */
public class Process {

	private final static CLogger classLog = CLogger.getCLogger(Process.class);

	private Process() {
	}
	
	/**
	 * 
	 * @param process
	 * @param jsonObject process instance configs
	 * @param runAsJob
	 * @return MPInstance
	 */
	public static MPInstance createPInstance(MProcess process, JsonObject jsonObject, boolean runAsJob) {
		MPInstance pInstance = new MPInstance(process, 0, 0, null);
		MPInstancePara[] iParams = pInstance.getParameters();
		for(MPInstancePara iParam : iParams) {
			MProcessPara processPara = process.getParameter(iParam.getParameterName());
			String columnName = processPara.getColumnName();
			String propertyName = TypeConverterUtils.toPropertyName(columnName);
			GridFieldVO gridFieldVO = GridFieldVO.createParameter(Env.getCtx(), 0, 0, 0, 0, columnName, processPara.getName(), processPara.getAD_Reference_ID(), 
					processPara.getAD_Reference_Value_ID(), processPara.isMandatory(), false, "");
			gridFieldVO.AD_Column_ID = processPara.getAD_Process_Para_ID();
			GridField gridField = new GridField(gridFieldVO);
			JsonElement element = jsonObject.get(propertyName);
			if (element == null)
				element = jsonObject.get(columnName);
			if (element != null) {			
				Object value = TypeConverterUtils.fromJsonValue(gridField, element);
				if (value != null) {
					if (value instanceof BigDecimal)
						iParam.setP_Number((BigDecimal)value);
					else if (value instanceof Number)
						iParam.setP_Number(((Number)value).intValue());
					else if (value instanceof Timestamp)
						iParam.setP_Date((Timestamp) value);
					else
						iParam.setP_String(value.toString());
				}
			}
			if (processPara.isRange()) {
				String toPropertyName = propertyName + "_to";
				element = jsonObject.get(toPropertyName);
				if (element == null)
					element = jsonObject.get(columnName + "_to");
				if (element != null) {			
					Object value = TypeConverterUtils.fromJsonValue(gridField, element);
					if (value != null) {
						if (value instanceof BigDecimal)
							iParam.setP_Number_To((BigDecimal)value);
						else if (value instanceof Number)
							iParam.setP_Number_To(((Number)value).intValue());
						else if (value instanceof Timestamp)
							iParam.setP_Date_To((Timestamp) value);
						else
							iParam.setP_String_To(value.toString());
					}
				}
			}
			if (iParam.is_Changed())
				iParam.saveEx();
		}
		
		if (runAsJob) {
			pInstance.setIsRunAsJob(true);
			pInstance.setIsProcessing(true);
			JsonElement notificationTypeElement= jsonObject.get("notification-type");
			if (notificationTypeElement != null && notificationTypeElement.isJsonPrimitive()) {
				String notificationType = notificationTypeElement.getAsString();
				if (notificationType.equals(MPInstance.NOTIFICATIONTYPE_EMail) || notificationType.equals(MPInstance.NOTIFICATIONTYPE_Notice) || notificationType.equals(MPInstance.NOTIFICATIONTYPE_EMailPlusNotice))
					pInstance.setNotificationType(notificationType);
				else
					pInstance.setNotificationType(MPInstance.NOTIFICATIONTYPE_EMailPlusNotice);
			} else {
				pInstance.setNotificationType(MPInstance.NOTIFICATIONTYPE_EMailPlusNotice);
			}
		}
		
		//Save Process Instance
		pInstance.saveEx();

		return pInstance;
	}
	
	/**
	 * 
	 * @param process
	 * @param pInstance
	 * @param jsonObject process info configs
	 * @return ProcessInfo
	 */
	public static ProcessInfo createProcessInfo(MProcess process, MPInstance pInstance, JsonObject jsonObject) {
		ProcessInfo processInfo = new ProcessInfo(process.getName(), process.getAD_Process_ID());
		processInfo.setAD_PInstance_ID(pInstance.getAD_PInstance_ID());
		JsonElement recordIdElement = jsonObject.get("record-id");
		if (recordIdElement != null && recordIdElement.isJsonPrimitive()) {
			int Record_ID = recordIdElement.getAsInt();
			if (Record_ID > 0) {
				processInfo.setRecord_ID(Record_ID);
				JsonElement tableIdElement = jsonObject.get("table-id");
				int AD_Table_ID = 0;
				if (tableIdElement != null && tableIdElement.isJsonPrimitive()) {
					AD_Table_ID = tableIdElement.getAsInt();
				}
				if (AD_Table_ID==0) {
					JsonElement tableNameElement = jsonObject.get("model-name");
					if (tableNameElement != null && tableNameElement.isJsonPrimitive()) {
						MTable table = MTable.get(Env.getCtx(), tableNameElement.getAsString());
						if (table != null)
							AD_Table_ID = table.getAD_Table_ID();
					}
				}
				if (AD_Table_ID > 0)
					processInfo.setTable_ID(AD_Table_ID);
			}
		}
		if (process.isReport()) {
			JsonElement reportTypeElement = jsonObject.get("report-type");
			if (reportTypeElement != null && reportTypeElement.isJsonPrimitive()) {
				processInfo.setReportType(reportTypeElement.getAsString());
			}
			JsonElement isSummaryElement = jsonObject.get("is-summary");
			if (isSummaryElement != null && isSummaryElement.isJsonPrimitive()) {
				processInfo.setIsSummary(isSummaryElement.getAsBoolean());
			}
		}
		
		processInfo.setAD_User_ID(Env.getAD_User_ID(Env.getCtx()));
		processInfo.setAD_Client_ID(Env.getAD_Client_ID(Env.getCtx()));
		processInfo.setAD_PInstance_ID(pInstance.getAD_PInstance_ID());
		processInfo.setAD_Process_UU(process.getAD_Process_UU());
		processInfo.setIsBatch(true);
		processInfo.setPrintPreview(true);
		processInfo.setExport(true);
		JsonElement printFormatIdElement = jsonObject.get("print-format-id");
		if (printFormatIdElement != null && printFormatIdElement.isJsonPrimitive()) {
			String printFormatStr = printFormatIdElement.getAsString();
			boolean isUUID = Util.isUUID(printFormatStr);
			int AD_PrintFormat_ID = isUUID ? getPrintFormatId(printFormatStr) : Integer.valueOf(printFormatStr);
			if (AD_PrintFormat_ID > 0) 
			{
				MPrintFormat format = new MPrintFormat(Env.getCtx(), AD_PrintFormat_ID, null);
				processInfo.setSerializableObject(format);
			}
		}
		
		if (!Util.isEmpty(process.getJasperReport())) 
		{
			if ("HTML".equals(processInfo.getReportType())) 
				processInfo.setExportFileExtension("html");
			else if ("CSV".equals(processInfo.getReportType()))
				processInfo.setExportFileExtension("csv");
			else if ("XLS".equals(processInfo.getReportType()))
				processInfo.setExportFileExtension("xls");
			else
				processInfo.setExportFileExtension("pdf");
		}
		return processInfo;
	}
	
	/**
	 * 
	 * @param processInfo
	 * @param processSlug
	 * @return JsonObject
	 */
	public static JsonObject toJsonObject(ProcessInfo processInfo, String processSlug) {
		JsonObject processInfoJson = new JsonObject();
		processInfoJson.addProperty("AD_PInstance_ID", processInfo.getAD_PInstance_ID());
		processInfoJson.addProperty("process", processSlug);
		processInfoJson.addProperty("summary", processInfo.getSummary());
		
		String data = processInfo.getJsonData();
		if (data != null && !data.isEmpty()) {
			JsonElement dataElement;
			try {
				dataElement = JsonParser.parseString(data);
				processInfoJson.add("data", dataElement.getAsJsonObject());
			} catch (Exception e) {
				dataElement = null;
				classLog.warning(e.getMessage());
			}
		}
		
		processInfoJson.addProperty("isError", processInfo.isError());
		if (processInfo.getPDFReport() != null) {
			File file = processInfo.getPDFReport();
			String property = "reportFile";
			addFile(processInfoJson, file, property, "pdf");
		}
		if (processInfo.getExportFile() != null) {
			File file = processInfo.getExportFile();
			String extension = processInfo.getExportFileExtension();
			String property = "exportFile";
			addFile(processInfoJson, file, property, extension);
		}
		IClusterService service = ClusterUtil.getClusterService();
		if (service != null && service.getLocalMember() != null) {
			processInfoJson.addProperty("nodeId", service.getLocalMember().getId());
		}
		
		ProcessInfoUtil.setLogFromDB(processInfo);
		ProcessInfoLog[] logs = processInfo.getLogs();
		if (logs != null && logs.length > 0) {
			JsonArray logArray = new JsonArray();
			SimpleDateFormat dateFormat = DisplayType.getDateFormat(DisplayType.Date);
			for(ProcessInfoLog log : logs) {
				Map<String, Object> logMap = new HashMap<String, Object>();
				
				if (log.getP_Date() != null)
					logMap.put("date", dateFormat.format(log.getP_Date()));
				//
				if (log.getP_Number() != null)
					logMap.put("number",log.getP_Number());
				//
				String logData = log.getJsonData();
				if (logData != null && !logData.isEmpty()) {
					JsonElement dataElement;
					try {
						dataElement = JsonParser.parseString(logData);
						logMap.put("data", dataElement.getAsJsonObject());
					} catch (Exception e) {
						dataElement = null;
					}
				}
				//
				if (log.getP_Msg() != null)
					logMap.put("msg",Msg.parseTranslation(Env.getCtx(), log.getP_Msg()));
				
		        Gson gson = new Gson();
		        JsonObject jsonObject = gson.toJsonTree(logMap).getAsJsonObject();

				logArray.add(jsonObject);
			}
			processInfoJson.add("logs", logArray);
		}
		
		return processInfoJson;
	}

	private static void addFile(JsonObject processInfoJson, File file, String property, String extension) {
		JsonElement jsonElement = null;
		try {
			if ("json".equals(extension)) {
				FileReader reader = new FileReader(file.getAbsolutePath());
				JsonReader jsonReader = new JsonReader(reader);
				jsonElement = JsonParser.parseReader(jsonReader);
			} else {
				// treat as binary
				ByteArrayOutputStream ba= loadFile(file);
				String base64String = StringUtils.newStringUtf8(Base64.encodeBase64(ba.toByteArray()));
				jsonElement = new JsonPrimitive(base64String);
			}
		} catch (JsonIOException | JsonSyntaxException | FileNotFoundException e) {
			throw new AdempiereException("Could not create JSON element from file " + file.getAbsolutePath(), e);
		}
		if (jsonElement != null)
			processInfoJson.add(property, jsonElement);
		processInfoJson.addProperty(property + "Name", file.getName());
		processInfoJson.addProperty(property + "Length", file.length());
	}

	private static ByteArrayOutputStream loadFile(File file) throws FileNotFoundException {
		FileInputStream fis = new FileInputStream(file);
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		byte[] buf = new byte[1024];
		try {
			for (int readNum; (readNum = fis.read(buf)) != -1;) {
				bos.write(buf, 0, readNum); //no doubt here is 0
			}
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		return bos;
	}

	private static int getPrintFormatId(String uuid) {
		String sql = "SELECT AD_PrintFormat_ID FROM AD_PrintFormat WHERE AD_PrintFormat_UU = ?";
		return DB.getSQLValue(null, sql, uuid);
	}

}
