/*
 * Copyright 2012 LinkedIn Corp.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package azkaban.trigger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.MapContext;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;

public class Condition {
	
	private static Logger logger = Logger.getLogger(Condition.class);
	
	private static JexlEngine jexl = new JexlEngine();
	private static CheckerTypeLoader checkerLoader = null;
	private Expression expression;
	private Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
	private MapContext context = new MapContext();
	private Long nextCheckTime = -1L;	
	
	public Condition(Map<String, ConditionChecker> checkers, String expr) {
		setCheckers(checkers);
		this.expression = jexl.createExpression(expr);
		updateNextCheckTime();
	}
	
	public Condition(Map<String, ConditionChecker> checkers, String expr, long nextCheckTime) {
		this.nextCheckTime = nextCheckTime;
		setCheckers(checkers);
//		for(ConditionChecker ck : checkers.values()) {
//			ck.setCondition(this);
//		}
		this.expression = jexl.createExpression(expr);
	}
	
	public synchronized static void setJexlEngine(JexlEngine jexl) {
		Condition.jexl = jexl;
	}
	
	public synchronized static void setCheckerLoader(CheckerTypeLoader loader) {
		Condition.checkerLoader = loader;
	}
	
	protected static CheckerTypeLoader getCheckerLoader() {
		return checkerLoader;
	}
	
	protected void registerChecker(ConditionChecker checker) {
		checkers.put(checker.getId(), checker);
		context.set(checker.getId(), checker);
		updateNextCheckTime();
	}
	
	public long getNextCheckTime() {
		return nextCheckTime;
	}
	
	public Map<String, ConditionChecker> getCheckers() {
		return this.checkers;
	}
	
	public void setCheckers(Map<String, ConditionChecker> checkers){
		this.checkers = checkers;
		for(ConditionChecker checker : checkers.values()) {
			this.context.set(checker.getId(), checker);
//			checker.setCondition(this);
		}
		updateNextCheckTime();
	}
	
	public void updateCheckTime(Long ct) {
		if(nextCheckTime < ct) {
			nextCheckTime = ct;
		}
	}
	
	private void updateNextCheckTime() {
		long time = Long.MAX_VALUE;
		for(ConditionChecker checker : checkers.values()) {
			time = Math.min(time, checker.getNextCheckTime());
		}
		this.nextCheckTime = time;
	}
	
	public void resetCheckers() {
		for(ConditionChecker checker : checkers.values()) {
			checker.reset();
		}
		updateNextCheckTime();
		logger.info("Done resetting checkers. The next check time will be " + new DateTime(nextCheckTime));
	}
	
	public String getExpression() {
		return this.expression.getExpression();
	}
	
	public void setExpression(String expr) {
		this.expression = jexl.createExpression(expr);
	}
	
	public boolean isMet() {
		logger.info("Testing condition " + expression);
		return expression.evaluate(context).equals(Boolean.TRUE);
	}
	
	public Object toJson() {
		Map<String, Object> jsonObj = new HashMap<String, Object>();
		jsonObj.put("expression", expression.getExpression());
		
		List<Object> checkersJson = new ArrayList<Object>();
		for(ConditionChecker checker : checkers.values()) {
			Map<String, Object> oneChecker = new HashMap<String, Object>();
			oneChecker.put("type", checker.getType());
			oneChecker.put("checkerJson", checker.toJson());
			checkersJson.add(oneChecker);
		}
		jsonObj.put("checkers", checkersJson);
		jsonObj.put("nextCheckTime", String.valueOf(nextCheckTime));
		
		return jsonObj;
	}

	@SuppressWarnings("unchecked")
	public static Condition fromJson(Object obj) throws Exception {
		if(checkerLoader == null) {
			throw new Exception("Condition Checker loader not initialized!");
		}
		
		Map<String, Object> jsonObj = (HashMap<String, Object>) obj;
		Condition cond = null;
		
		try {
			Map<String, ConditionChecker> checkers = new HashMap<String, ConditionChecker>();
			List<Object> checkersJson = (List<Object>) jsonObj.get("checkers");			
			for(Object oneCheckerJson : checkersJson) {
				Map<String, Object> oneChecker = (HashMap<String, Object>) oneCheckerJson;
				String type = (String) oneChecker.get("type");
				ConditionChecker ck = checkerLoader.createCheckerFromJson(type, oneChecker.get("checkerJson"));
				checkers.put(ck.getId(), ck);
			}
			String expr = (String) jsonObj.get("expression");
			Long nextCheckTime = Long.valueOf((String) jsonObj.get("nextCheckTime"));
				
			cond = new Condition(checkers, expr, nextCheckTime);
			
		} catch(Exception e) {
			e.printStackTrace();
			logger.error("Failed to recreate condition from json.", e);
			throw new Exception("Failed to recreate condition from json.", e);
		}
		
		return cond;
	}
	

}
