/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.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://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.web.taglib;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.servlet.jsp.tagext.TagSupport;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.Encounter;
import org.openmrs.Obs;
import org.openmrs.api.ConceptService;
import org.openmrs.api.PatientSetService;
import org.openmrs.api.context.Context;
import org.openmrs.util.OpenmrsUtil;

public class SummaryTest extends TagSupport {
	
	private static final long serialVersionUID = 102731753333L;
	
	private final Log log = LogFactory.getLog(getClass());
	
	private Collection<Obs> observations;
	
	private Collection<Encounter> encounters;
	
	private String var;
	
	private String ifTrue;
	
	private String ifFalse;
	
	public int doStartTag() {
		Boolean ret = false;
		if ((ifTrue == null || ifTrue.length() == 0) && (ifFalse == null || ifFalse.length() == 0)) {
			ret = true;
		} else {
			if (ifTrue != null && ifTrue.length() > 0)
				ret |= evaluate(ifTrue);
			if (ifFalse != null && ifFalse.length() > 0)
				ret |= !evaluate(ifFalse);
		}
		
		pageContext.setAttribute(var, ret);
		return SKIP_BODY;
	}
	
	public int doEndTag() {
		observations = null;
		var = null;
		ifTrue = null;
		ifFalse = null;
		return EVAL_PAGE;
	}
	
	private boolean evaluate(String expr) {
		expr = expr.trim();
		log.debug("evaluate " + expr);
		List<String> commands = new ArrayList<String>();
		{
			StringBuilder command = new StringBuilder();
			String[] lines = expr.split("\n");
			for (String line : lines) {
				if (line.trim().startsWith("!")) {
					if (command.length() > 0) {
						commands.add(command.toString());
						command = new StringBuilder();
					}
				}
				command.append(line.trim());
				command.append("\n");
			}
			if (command.length() > 0)
				commands.add(command.toString());
		}
		boolean andMode = true;
		List<Boolean> commandResults = new ArrayList<Boolean>();
		for (String s : commands) {
			String command = (new StringTokenizer(s.toUpperCase())).nextToken();
			if (command.equals("!AND")) {
				andMode = true;
			} else if (command.equals("!OR")) {
				andMode = false;
			} else if (command.equals("!OBSCHECK")) {
				s = s.substring("!OBSCHECK".length()).trim();
				commandResults.add(handleObsCheck(s));
			} else {
				throw new RuntimeException("Don't know how to handle command " + command + "\n" + s);
			}
		}
		
		boolean ret = andMode ? true : false;
		for (Boolean b : commandResults) {
			if (andMode)
				ret &= b;
			else
				ret |= b;
		}
		return ret;
	}
	
	private boolean handleObsCheck(String expr) {
		log.debug("handleObsCheck(" + expr + ")");
		expr = expr.trim();
		if (expr.length() == 0)
			return true;
		
		Map<String, String> args = new HashMap<String, String>();
		String[] lines = expr.split("\n");
		for (String s : lines) {
			s = s.trim();
			int ind = s.indexOf(':');
			String key = s.substring(0, ind).toLowerCase();
			String val = s.substring(ind + 1);
			args.put(key, val);
			log.debug(key + " -> " + val);
		}
		
		PatientSetService.TimeModifier test = PatientSetService.TimeModifier.ANY;
		Set<Concept> conceptsOfInterest = new HashSet<Concept>();
		Date fromDate = null;
		Date toDate = null;
		if (args.containsKey("test"))
			test = PatientSetService.TimeModifier.valueOf(args.get("test").trim().toUpperCase());
		String conceptName = args.get("concept");
		if (conceptName == null)
			throw new IllegalArgumentException("You must specify a concept");
		{
			ConceptService cs = Context.getConceptService();
			boolean isSet = conceptName.startsWith("set:");
			if (isSet)
				conceptName = conceptName.substring("set:".length());
			Concept c = cs.getConceptByName(conceptName);
			if (c == null) {
				log.warn("Can't find concept " + conceptName);
			} else {
				if (isSet)
					conceptsOfInterest.addAll(cs.getConceptsByConceptSet(c));
				else
					conceptsOfInterest.add(c);
			}
		}
		
		if (args.containsKey("timespan")) {
			// [last|next defaults to last] [# defaults to 1] [m|d|y defaults to m]
			boolean inPast = true;
			int timeUnit = Calendar.MONTH;
			Integer time = 1;
			String ts = args.get("timespan");
			String[] s = ts.split(" ");
			for (String str : s) {
				if (str.length() == 0)
					continue;
				if (str.startsWith("l"))
					inPast = true;
				else if (str.startsWith("n"))
					inPast = false;
				else if (str.startsWith("m"))
					timeUnit = Calendar.MONTH;
				else if (str.startsWith("d"))
					timeUnit = Calendar.DAY_OF_MONTH;
				else if (str.startsWith("y"))
					timeUnit = Calendar.YEAR;
				else
					time = Integer.valueOf(str);
			}
			Calendar c = Calendar.getInstance();
			c.add(timeUnit, (inPast ? -1 : 1) * time);
			if (inPast)
				fromDate = c.getTime();
			else
				toDate = c.getTime();
		}
		
		log.debug("test:" + test);
		log.debug("concepts of interest:" + conceptsOfInterest);
		log.debug("fromDate:" + fromDate);
		log.debug("toDate:" + toDate);
		
		List<Obs> obsThatMatter = new ArrayList<Obs>();
		for (Obs o : observations) {
			if (conceptsOfInterest.contains(o.getConcept())
			        && (fromDate == null || OpenmrsUtil.compare(fromDate, o.getObsDatetime()) <= 0)
			        && (toDate == null || OpenmrsUtil.compare(o.getObsDatetime(), toDate) <= 0)) {
				obsThatMatter.add(o);
			}
		}
		log.debug("obsThatMatter (" + obsThatMatter.size() + "): " + obsThatMatter);
		if (test == PatientSetService.TimeModifier.ANY) {
			return obsThatMatter.size() > 0;
		} else if (test == PatientSetService.TimeModifier.NO) {
			return obsThatMatter.size() == 0;
		} else {
			throw new RuntimeException("Can't handle test:" + test);
		}
	}
	
	public String getIfFalse() {
		return ifFalse;
	}
	
	public void setIfFalse(String ifFalse) {
		this.ifFalse = ifFalse;
	}
	
	public String getIfTrue() {
		return ifTrue;
	}
	
	public void setIfTrue(String ifTrue) {
		this.ifTrue = ifTrue;
	}
	
	public Collection<Obs> getObservations() {
		return observations;
	}
	
	public void setObservations(Collection<Obs> observations) {
		this.observations = observations;
	}
	
	public String getVar() {
		return var;
	}
	
	public void setVar(String var) {
		this.var = var;
	}
	
	public Collection<Encounter> getEncounters() {
		return encounters;
	}
	
	public void setEncounters(Collection<Encounter> encounters) {
		this.encounters = encounters;
	}
	
}
