/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2012 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * Andy White
#
************************************************************************ */
package divconq.lang;

import java.util.ArrayList;
import java.util.List;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

import divconq.bus.Message;
import divconq.log.DebugLevel;
import divconq.struct.FieldStruct;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.util.StringUtil;

/**
 * Provides info about the success of a method call - like a boolean return - only with more info.
 * Check "hasErrors" to see if call failed.  Also there is an error code, and a log of messages.
 * That log of messages will also get written to the debug logger using the TaskContext of the caller.
 * It is also possible to track progress using a step count and/or a percent count.
 * 
 * @author Andy
 *
 */
public class OperationResult {
	// primary error code and message (info and warning not in here)
	protected long code = 0;		// error code, non-zero means error, only first error code is tracked 
	protected String message = null;		// error code, non-zero means error, first code is tracked 
	protected List<RecordStruct> messages = new ArrayList<RecordStruct>();
	
    // progress tracking
    protected long progTotalSteps = 0;
    protected long progCurrStep = 0;
    protected String progStepName = null;
    protected long progComplete = 0;	
    protected String progMessage = null;
    
    protected TaskContext tcontext = null;
    protected boolean autolog = false;
    
    /**
     * @param autolog true if messages should be copied to dcLogger
     */
    public OperationResult(boolean autolog) {
    	this.autolog = autolog;
    	this.tcontext = TaskContext.get();    	
    	
    	if (this.tcontext == null)
    		throw new IllegalArgumentException("Operation Result requires a task context");
    }
	
	/**
	 * @return error or exit code if any, 0 otherwise
	 */
	public long getCode() {
		return this.code;
	}
	
	/**
	 * @return error or exit message, if any
	 */
	public String getMessage() {
		return this.message;
	}
	
	/**
	 * @return all messages logged with this call (and possibly sub calls made within the call)
	 */
	public ListStruct getMessages() {
		return new ListStruct(this.messages.toArray());
	}
	
	/**
	 * @param code code for message
	 * @param msg message
	 */
	public void trace(long code, String msg) {
		this.log(DebugLevel.Trace, code, msg);
	}
	
	/**
	 * @param code code for message
	 * @param msg message
	 */
	public void info(long code, String msg) {		
		this.log(DebugLevel.Info, code, msg);
	}
	
	/**
	 * @param code code for message
	 * @param msg message
	 */
	public void warn(long code, String msg) {
		this.log(DebugLevel.Warn, code, msg);
	}
	
	/**
	 * @param code code for message
	 * @param msg message
	 */
	public void error(long code, String msg) {
		this.log(DebugLevel.Error, code, msg);
	}
	
	/**
	 * Overrides any previous return codes and messages
	 * 
	 * @param code code for message
	 * @param msg message
	 */
	public void exit(long code, String msg) {
		this.code = code;
		this.message = msg;
		
		this.log(DebugLevel.Info, code, msg);
	}
	
	/**
	 * @param code for message translation token
	 * @param params for message translation
	 */
	public void traceTr(long code, Object... params) {
		String msg = this.tcontext.tr("_code_" + code, params);
		this.log(DebugLevel.Trace, code, msg);
	}
	
	/**
	 * @param code for message translation token
	 * @param params for message translation
	 */
	public void infoTr(long code, Object... params) {		
		String msg = this.tcontext.tr("_code_" + code, params);
		this.log(DebugLevel.Info, code, msg);
	}
	
	/**
	 * @param code for message translation token
	 * @param params for message translation
	 */
	public void warnTr(long code, Object... params) {
		String msg = this.tcontext.tr("_code_" + code, params);
		this.log(DebugLevel.Warn, code, msg);
	}
	
	/**
	 * @param code for message translation token
	 * @param params for message translation
	 */
	public void errorTr(long code, Object... params) {
		String msg = this.tcontext.tr("_code_" + code, params);
		this.log(DebugLevel.Error, code, msg);
	}
	
	/**
	 * Overrides any previous return codes and messages
	 * 
	 * @param code for message translation token
	 * @param params for message translation
	 */
	public void exitTr(long code, Object... params) {
		String msg = this.tcontext.tr("_code_" + code, params);
		
		this.code = code;
		this.message = msg;
		
		this.log(DebugLevel.Info, code, msg);
	}
	
	/**
	 * @param lvl level of message
	 * @param code for message
	 * @param msg text of message
	 */
	public void log(DebugLevel lvl, long code, String msg) {
		this.messages.add(
				new RecordStruct(
						new FieldStruct("Occur", new DateTime(DateTimeZone.UTC)),
						new FieldStruct("Level", lvl.toString()),
						new FieldStruct("Code", code),
						new FieldStruct("Message", msg)
				)
		);
		
		if ((lvl == DebugLevel.Error) && (this.code == 0)) {
			this.code = code;
			this.message = msg;
		}		
		
		if (this.autolog) {
			// copy the message to logger 
			if (code > 0)
				this.tcontext.log(lvl, msg, "Code", code + "");
			else
				this.tcontext.log(lvl, msg);
		}
	}
    
    /**
     * Add a logging boundary, delineating a new section of work for this task
     * 
     * @param tags identity of this boundry
     */
    public void boundary(String... tags) {
    	this.tcontext.logBoundary(tags);
    }

	/**
	 * @param res take the result and combine its messages with this result
	 */
	public void copyMessages(OperationResult res) {
		for (RecordStruct msg :  res.messages) {
			this.messages.add(msg);
			
			if ("Error".equals(msg.getFieldAsString("Level"))) {
				if (this.code == 0) {
					this.code = msg.getFieldAsInteger("Code");
					this.message = msg.getFieldAsString("Message");
				}
			}
		}		
	}

	/**
	 * @return create a message for the bus that holds this result, useful for service results
	 */
	public Message toMessage() {
		Message m = new Message();
		
		m.setField("Result", this.code);
		
		if (StringUtil.isNotEmpty(this.message)) 
			m.setField("Message", this.message);
		
		m.setField("Messages", this.getMessages());		
		
		return m;
	}
	
	@Override
	public String toString() {
		return this.messages.toString(); 
	}

	/**
	 * @return true if an error code is present
	 */
	public boolean hasErrors() {
		return (this.code > 0);
	}

	// progress methods
	
	/**
	 * @return units/percentage of task completed
	 */
	public long getAmountCompleted() {
		return this.progComplete; 
	}
	
	/**
	 * @param v units/percentage of task completed
	 */
	public void setAmountCompleted(long v) { 
		this.progComplete = v; 
	}

	/**
	 * @return status message about task progress
	 */
	public String getProgressMessage() {
		return this.progMessage; 
	}
	
	/**
	 * @param v status message about task progress
	 */
	public void setProgressMessage(String v) { 
		this.progMessage = v; 
	}

	/**
	 * @return total steps for this specific task
	 */
	public long getSteps() { 
		return this.progTotalSteps; 
	}
	
	/**
	 * @param v total steps for this specific task
	 */
	public void setSteps(long v) { 
		this.progTotalSteps = v; 
	}

	/**
	 * @return current step within this specific task
	 */
	public long getCurrentStep() { 
		return this.progCurrStep; 
	}
	
	/**
	 * @param v current step within this specific task
	 */
	public void setCurrentStep(long v) { 
		this.progCurrStep = v; 
	}

	/**
	 * @return name of current step
	 */
	public String getCurrentStepName() { 
		return this.progStepName; 
	}
	
	/**
	 * @param v name of current step
	 */
	public void setCurrentStepName(String v) {
		this.progStepName = v; 
	}    
}
