package org.exist.client.xacml;

import com.sun.xacml.cond.FunctionBase;
import com.sun.xacml.cond.FunctionFactory;
import com.sun.xacml.cond.MatchFunction;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.exist.Namespaces;
import org.exist.security.xacml.XACMLConstants;

import org.apache.log4j.Logger;

/**
 * Abbreviates the constants used in XACML to something
 * shorter and hopefully more user-friendly.  The getFullXXX
 * methods should generally only be called with an argument
 * obtained as the result of the corresponding getAbbreviatedXXX
 * method.
 */
public class Abbreviator
{
	private static final Logger LOG = Logger.getLogger(Abbreviator.class);
	private static final String[][] comparisonMap = { {"equal", "="},
			{"less-than-or-equal", "<="}, {"less-than", "<"},
			{"greater-than-or-equal", ">="}, {"greater-than", ">"} };
	
	private Map attributeIdMap = new HashMap(8);
	private Map typeMap = new HashMap(8);
	private Map functionMap = new HashMap(8);
	
	public Abbreviator() {} 
	
	public String getAbbreviatedId(URI uri)
	{
		if(uri == null)
			return null;
		String toString = uri.toString();
		if(toString.startsWith(XACMLConstants.EXIST_XACML_NS))
		{
			toString = toString.substring(XACMLConstants.EXIST_XACML_NS.length());
			int i = toString.lastIndexOf('#');
			if(i == -1)
			{
				i = toString.lastIndexOf('/');
				if(i != -1)
					toString = toString.substring(i+1);
			}
			else
				toString = toString.substring(i+1);
			
		}
		else if(toString.startsWith(XACMLConstants.VERSION_1_0_BASE))
		{
			toString = toString.substring(XACMLConstants.VERSION_1_0_BASE.length());
			int i = toString.lastIndexOf(':');
			if(i != -1)
				toString = toString.substring(i+1);
		}
		else
			return toString;
		
		attributeIdMap.put(toString, uri);
		return toString;
	}
	public URI getFullIdURI(String abbrev)
	{
		return get(attributeIdMap, abbrev); 
	}
	
	public String getAbbreviatedType(URI type)
	{
		if(type == null)
			return null;
		String toString = type.toString();
		if(toString.startsWith(XACMLConstants.XACML_DATATYPE_BASE))
			toString = toString.substring(XACMLConstants.XACML_DATATYPE_BASE.length());
		else if(toString.startsWith(Namespaces.SCHEMA_NS))
			toString = toString.substring(Namespaces.SCHEMA_NS.length()+1);
		else if(toString.startsWith(XACMLConstants.XQUERY_OPERATORS_NS))
			toString = toString.substring(XACMLConstants.XQUERY_OPERATORS_NS.length()+1);
		else
			return toString;
		
		typeMap.put(toString, type);
		return toString;
	}
	public URI getFullTypeURI(String abbrev)
	{
		return get(typeMap, abbrev);
	}
	
	public String getAbbreviatedCombiningID(URI uri)
	{
		if(uri == null)
			return null;
		String toString = uri.toString();
		if(toString.startsWith(XACMLConstants.RULE_COMBINING_BASE))
			toString = toString.substring(XACMLConstants.RULE_COMBINING_BASE.length());
		else if(toString.startsWith(XACMLConstants.POLICY_COMBINING_BASE))
			toString = toString.substring(XACMLConstants.POLICY_COMBINING_BASE.length());
		else
			return toString;
		
		return toString;
	}
	public URI getFullCombiningURI(String abbrev, boolean isRuleAlg)
	{
		if(abbrev == null)
			return null;
		String prefix = isRuleAlg ? XACMLConstants.RULE_COMBINING_BASE : XACMLConstants.POLICY_COMBINING_BASE;
		return URI.create(prefix + abbrev);
	}
	
	public String getAbbreviatedFunctionId(URI functionId)
	{
		if(functionId == null)
			return null;
	
		String toString = functionId.toString();
		if(toString.startsWith(FunctionBase.FUNCTION_NS))
			toString = toString.substring(FunctionBase.FUNCTION_NS.length());
		else
		{
			functionMap.put(functionId, toString);
			return toString;
		}
		
		if(toString.equals("regexp-string-match"))
			toString = "string-match";
		
		for(int i = 0; i < comparisonMap.length; ++i)
		{
			if(toString.endsWith(comparisonMap[i][0]))
			{
				toString = comparisonMap[i][1];
				return toString;
			}
		}
		functionMap.put(functionId, toString);
		return toString;
	}
	public URI getFullFunctionId(String abbrev, URI dataType)
	{
		if(abbrev == null || dataType == null)
			return null;
		URI uri = (URI)functionMap.get(abbrev);
		if(uri != null)
			return uri;
		
		String abbrevType = getAbbreviatedType(dataType);
		
		if(abbrev.equals("match") && abbrevType.equals("string"))
			return URI.create(MatchFunction.NAME_REGEXP_STRING_MATCH);
		
		for(int i = 0; i < comparisonMap.length; ++i)
		{
			if(abbrev.equals(comparisonMap[i][1]))
			{
				abbrev = abbrevType + "-" + comparisonMap[i][0];
				break;
			}
		}
		return URI.create(FunctionBase.FUNCTION_NS + abbrev);
	}

	//TODO: not valid for date-related conversions
	public Set getAbbreviatedTargetFunctions(URI dataType)
	{
		//note that sunxacml includes logical functions in the target
		//instance
		//this is almost certainly wrong
		//abbrevTargetFunctionId filters these out because they do
		//not include the data type in their name (not, and, or, n-or)
		FunctionFactory factory = FunctionFactory.getTargetInstance();
		Set functionIds = factory.getSupportedFunctions();
		
		Set ret = new HashSet();
		String abbrevType = getAbbreviatedType(dataType);
		String functionId;
		for(Iterator it = functionIds.iterator(); it.hasNext();)
		{
			functionId = (String)it.next();
			functionId = abbrevTargetFunctionId(functionId, abbrevType);
			if(functionId != null)
				ret.add(functionId);
		}
		return ret;
	}
	public String getAbbreviatedTargetFunctionId(URI functionId, URI dataType)
	{
		if(functionId == null || dataType == null)
			return null;
		return abbrevTargetFunctionId(functionId.toString(), getAbbreviatedType(dataType));
	}
	private String abbrevTargetFunctionId(String functionId, String abbrevType)
	{
		if(functionId.startsWith(FunctionBase.FUNCTION_NS))
			functionId = functionId.substring(FunctionBase.FUNCTION_NS.length());
		else
			return null;
		
		if(functionId.equals("regexp-string-match"))
			functionId = "string-match";
		if(functionId.startsWith(abbrevType))
		{
			functionId = functionId.substring(abbrevType.length() + 1);
			for(int i = 0; i < comparisonMap.length; ++i)
			{
				if(functionId.equals(comparisonMap[i][0]))
				{
					functionId = comparisonMap[i][1];
					return functionId;
				}
			}
			return functionId;
		}
		return null;
	}
	
	private static URI get(Map map, String abbrev)
	{
		URI ret = (URI)map.get(abbrev);
		return (ret == null) ? parse(abbrev) : ret;
	}
	private static URI parse(String abbrev)
	{
		try
		{
			return new URI(abbrev);
		}
		catch (URISyntaxException e)
		{
			LOG.warn("Invalid URI '" + abbrev + "'", e);
			return null;
		}
	}
}
