/************************************************************************************************
 *   ____________ _   _  _____          _      _____ _    _ _______          __   _ _      _   
 *  |___  /  ____| \ | |/ ____|        | |    / ____| |  | |_   _\ \        / /  | | |    | |  
 *     / /| |__  |  \| | |     __ _ ___| |__ | |  __| |  | | | |  \ \  /\  / /_ _| | | ___| |_ 
 *    / / |  __| | . ` | |    / _` / __| '_ \| | |_ | |  | | | |   \ \/  \/ / _` | | |/ _ \ __|
 *   / /__| |____| |\  | |___| (_| \__ \ | | | |__| | |__| |_| |_   \  /\  / (_| | | |  __/ |_ 
 *  /_____|______|_| \_|\_____\__,_|___/_| |_|\_____|\____/|_____|   \/  \/ \__,_|_|_|\___|\__|
 *                                                                                             
 * Copyright (c) 2016-2018 The ZEN Developers
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 **********************************************************************************/
package com.vaklinov.zcashui.msg;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Date;

import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.WriterConfig;
import com.vaklinov.zcashui.Util;


/**
 * Encapsulates a zen message
 */
public class Message
{
	public static enum DIRECTION_TYPE
	{
		SENT, RECEIVED
	};
	
	public static enum VERIFICATION_TYPE
	{
		UNVERIFIED, VERIFICATION_OK, VERIFICATION_FAILED 
	};
	
	// Wire protocol fields
	private Integer version;
	private String  from;
	private String  message;
	private String  sign;
	private String  threadID; // Thread ID for anonymous messages
	private String  returnAddress; // for anonymous messages
	
	// Additional internal fields - not to be used when transmitted over the wire
	private String            transactionID;
	private Date              time;
	private DIRECTION_TYPE    direction;
	private VERIFICATION_TYPE verification;
	private boolean           isAnonymous; // If the message is sent or received anonymously
		
	
	public Message(JsonObject obj)
	{
		this.copyFromJSONObject(obj);
	}
	
	
	public Message(File f)
		throws IOException
	{
		Reader r = null;
			
		try
		{
			r = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"));
			JsonObject obj = Util.parseJsonObject(r);
			
			this.copyFromJSONObject(obj);
		} finally
		{
			if (r != null)
			{
				r.close();
			}
		}
	}


	public void copyFromJSONObject(JsonObject obj)
	{
		// Wire protocol fields
		this.version       = obj.getInt("ver",              1);
		this.from          = obj.getString("from",          "");
		this.message       = obj.getString("message",       "");
		this.sign          = obj.getString("sign",          "");
		this.threadID      = obj.getString("threadid",      "");	
		this.returnAddress = obj.getString("returnaddress", "");
		
		// Additional fields - may be missing, get default values
		this.transactionID = obj.getString("transactionID", "");
		this.time          = new Date(obj.getLong("time",   0));
		this.direction     = DIRECTION_TYPE.valueOf(
				                 obj.getString("direction", DIRECTION_TYPE.RECEIVED.toString()));
		this.verification  = VERIFICATION_TYPE.valueOf(
				                 obj.getString("verification", VERIFICATION_TYPE.UNVERIFIED.toString()));
		
		if (obj.get("isanonymous") != null)
		{
			this.isAnonymous = obj.getBoolean("isanonymous", false);
		} else
		{
			// Determine from content if it is anonymous
			this.isAnonymous = obj.get("threadid") != null; 
		}
	}
	
	
	public JsonObject toJSONObject(boolean forWireProtocol)
	{
		JsonObject obj = new JsonObject();
		
		if (this.isAnonymous())
		{
			obj.set("ver",           version);
			obj.set("message",       nonNull(message));
			obj.set("threadid",      nonNull(threadID));
		} else
		{
			obj.set("ver",           version);
			obj.set("from",          nonNull(from));
			obj.set("message",       nonNull(message));
			obj.set("sign",          nonNull(sign));
		}
		
		if (!forWireProtocol)
		{
			obj.set("transactionID", nonNull(transactionID));
			obj.set("time",          this.time.getTime());
			obj.set("direction",     this.direction.toString());
			obj.set("verification",  this.verification.toString());
			obj.set("isanonymous",   isAnonymous);
		}
		
		return obj;
	}
	
	
	public void writeToFile(File f)
		throws IOException
	{
		Writer w = null;
		
		try
		{
			w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "UTF-8"));
			w.write(this.toJSONObject(false).toString(WriterConfig.PRETTY_PRINT));
		} finally
		{
			if (w != null)
			{
				w.close();
			}
		}
	}


	public Integer getVersion() 
	{
		return version;
	}


	public void setVersion(Integer version) 
	{
		this.version = version;
	}


	public String getFrom() 
	{
		return from;
	}


	public void setFrom(String from) 
	{
		this.from = from;
	}


	public String getMessage() 
	{
		return message;
	}


	public void setMessage(String message) 
	{
		this.message = message;
	}


	public String getSign() 
	{
		return sign;
	}


	public void setSign(String sign) 
	{
		this.sign = sign;
	}


	public String getTransactionID() 
	{
		return transactionID;
	}


	public void setTransactionID(String transactionID) 
	{
		this.transactionID = transactionID;
	}


	public Date getTime() 
	{
		return time;
	}


	public void setTime(Date time) 
	{
		this.time = time;
	}


	public DIRECTION_TYPE getDirection() 
	{
		return direction;
	}


	public void setDirection(DIRECTION_TYPE direction) 
	{
		this.direction = direction;
	}


	public VERIFICATION_TYPE getVerification() 
	{
		return verification;
	}


	public void setVerification(VERIFICATION_TYPE verification) 
	{
		this.verification = verification;
	}
	
	
	public boolean isAnonymous() 
	{
		return isAnonymous;
	}

	
	public void setAnonymous(boolean isAnonymous) 
	{
		this.isAnonymous = isAnonymous;
	}

	
	public String getThreadID() 
	{
		return threadID;
	}

	
	public void setThreadID(String threadID) 
	{
		this.threadID = threadID;
	}


	public String getReturnAddress() 
	{
		return returnAddress;
	}


	public void setReturnAddress(String returnAddress) 
	{
		this.returnAddress = returnAddress;
	}

	
	/**
	 * Verifies if a message represented as a JSON object is valid according to the
	 * ZEN messaging protocol:
	 * https://github.com/ZencashOfficial/messaging-protocol/blob/master/README.md
	 * 
	 * @param message
	 * 
	 * @return true if a message represented as a JSON object is valid according to the
	 * ZEN messaging protocol.
	 */
	public static boolean isValidZENMessagingProtocolMessage(JsonObject message)
	{
		if ((message == null) || (message.isEmpty()))
		{
			return false;
		}
		
		if (message.get("threadid") != null)
		{
			// Verify anonymous message
			int version          = message.getInt("ver",              -1);
			String msg           = message.getString("message",       "");
			String threadID      = message.getString("threadid",      "");

			return (version > 0)                   && 
				   (!Util.stringIsEmpty(threadID)) &&
				   (!Util.stringIsEmpty(msg));
		} else
		{
			// Verify normal message			
			int version          = message.getInt("ver",              -1);
			String from          = message.getString("from",          "");
			String msg           = message.getString("message",       "");
			String sign          = message.getString("sign",          "");
			
			return (version > 0)               && 
				   (!Util.stringIsEmpty(from)) &&
				   (!Util.stringIsEmpty(msg))  && 
				   (!Util.stringIsEmpty(sign));
		}
	}
	
	
	public String nonNull(String s)
	{
		return (s != null) ? s : "";
	}
}