package railo.commons.lang.mimetype;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import railo.commons.io.CharsetUtil;
import railo.commons.lang.StringUtil;
import railo.runtime.op.Caster;
import railo.runtime.type.UDF;
import railo.runtime.type.UDFPlus;
import railo.runtime.type.util.ListUtil;

public class MimeType {
	
	private static int DEFAULT_MXB=100000;
	private static double DEFAULT_MXT=5;
	private static double DEFAULT_QUALITY=1;
	private static Charset DEFAULT_CHARSET=null;
	
	public static final MimeType ALL = new MimeType(null,null,null);
	public static final MimeType APPLICATION_JSON = new MimeType("application","json",null);
	public static final MimeType APPLICATION_XML = new MimeType("application","xml",null);
	public static final MimeType APPLICATION_WDDX = new MimeType("application","wddx",null);
	public static final MimeType APPLICATION_CFML = new MimeType("application","cfml",null);
	public static final MimeType APPLICATION_PLAIN = new MimeType("application","lazy",null);

	public static final MimeType IMAGE_GIF = new MimeType("image","gif",null);
	public static final MimeType IMAGE_JPG = new MimeType("image","jpeg",null);
	public static final MimeType IMAGE_PNG = new MimeType("image","png",null);
	public static final MimeType IMAGE_TIFF = new MimeType("image","tiff",null);
	public static final MimeType IMAGE_BMP = new MimeType("image","bmp",null);
	public static final MimeType IMAGE_WBMP = new MimeType("image","vnd.wap.wbmp",null);
	public static final MimeType IMAGE_FBX = new MimeType("image","fbx",null);
	public static final MimeType IMAGE_PNM = new MimeType("image","x-portable-anymap",null);
	public static final MimeType IMAGE_PGM = new MimeType("image","x-portable-graymap",null);
	public static final MimeType IMAGE_PBM = new MimeType("image","x-portable-bitmap",null);
	public static final MimeType IMAGE_ICO = new MimeType("image","ico",null);
	public static final MimeType IMAGE_PSD = new MimeType("image","psd",null);
	public static final MimeType IMAGE_ASTERIX = new MimeType("image",null,null);
	public static final MimeType APPLICATION_JAVA = new MimeType("application","java",null);
	
	public static final MimeType TEXT_HTML = new MimeType("text","html",null);
	
	private String type;
	private String subtype;
	//private double quality;
	//private int mxb;
	//private double mxt;
	private Map<String,String> properties;
	private double q=-1;
	private Charset cs;
	private boolean initCS=true;

	
	private MimeType(String type, String subtype, Map<String,String> properties) {
		//if(quality<0 || quality>1)
		//	throw new RuntimeException("quality must be a number between 0 and 1, now ["+quality+"]");

		
		this.type=type;
		this.subtype=subtype;
		this.properties=properties;
		//this.quality=quality;
		//this.mxb=mxb;
		//this.mxt=mxt;
	}
	


	private static MimeType getInstance(String type, String subtype, Map<String,String> properties) {
		// TODO read this from a external File
		if("text".equals(type)) {
			if("xml".equals(subtype)) return new MimeType("application", "xml", properties);
			if("x-json".equals(subtype)) return new MimeType("application", "json", properties);
			if("javascript".equals(subtype)) return new MimeType("application", "json", properties);
			if("x-javascript".equals(subtype)) return new MimeType("application", "json", properties);
			if("wddx".equals(subtype)) return new MimeType("application", "wddx", properties);
		}
		else if("application".equals(type)) {
			if("x-json".equals(subtype)) return new MimeType("application", "json", properties);
			if("javascript".equals(subtype)) return new MimeType("application", "json", properties);
			if("x-javascript".equals(subtype)) return new MimeType("application", "json", properties);
			
			if("jpg".equals(subtype)) return new MimeType("image", "jpeg", properties);
			if("x-jpg".equals(subtype)) return new MimeType("image", "jpeg", properties);
			
			if("png".equals(subtype)) return new MimeType("image", "png", properties);
			if("x-png".equals(subtype)) return new MimeType("image", "png", properties);

			if("tiff".equals(subtype)) return new MimeType("image", "tiff", properties);
			if("tif".equals(subtype)) return new MimeType("image", "tiff", properties);
			if("x-tiff".equals(subtype)) return new MimeType("image", "tiff", properties);
			if("x-tif".equals(subtype)) return new MimeType("image", "tiff", properties);

			if("fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
			if("x-fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
			if("vnd.fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
			if("vnd.netfpx".equals(subtype)) return new MimeType("image", "fpx", properties);
			
			if("ico".equals(subtype)) return new MimeType("image", "ico", properties);
			if("x-ico".equals(subtype)) return new MimeType("image", "ico", properties);
			if("x-icon".equals(subtype)) return new MimeType("image", "ico", properties);
			
			if("psd".equals(subtype)) return new MimeType("image", "psd", properties);
			if("x-photoshop".equals(subtype)) return new MimeType("image", "psd", properties);
			if("photoshop".equals(subtype)) return new MimeType("image", "psd", properties);
		}
		else if("image".equals(type)) {
			if("gi_".equals(subtype)) return new MimeType("image", "gif", properties);
			
			if("pjpeg".equals(subtype)) return new MimeType("image", "jpeg", properties);
			if("jpg".equals(subtype)) return new MimeType("image", "jpeg", properties);
			if("jpe".equals(subtype)) return new MimeType("image", "jpeg", properties);
			if("vnd.swiftview-jpeg".equals(subtype)) return new MimeType("image", "jpeg", properties);
			if("pipeg".equals(subtype)) return new MimeType("image", "jpeg", properties);
			if("jp_".equals(subtype)) return new MimeType("image", "jpeg", properties);

			if("x-png".equals(subtype)) return new MimeType("image", "png", properties);

			if("tif".equals(subtype)) return new MimeType("image", "tiff", properties);
			if("x-tif".equals(subtype)) return new MimeType("image", "tiff", properties);
			if("x-tiff".equals(subtype)) return new MimeType("image", "tiff", properties);

			if("x-fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
			if("vnd.fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
			if("vnd.netfpx".equals(subtype)) return new MimeType("image", "fpx", properties);

			if("x-portable/graymap".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
			if("portable graymap".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
			if("x-pnm".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
			if("pnm".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);

			if("x-portable/graymap".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
			if("portable graymap".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
			if("x-pgm".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
			if("pgm".equals(subtype)) return new MimeType("image", "x-portable-graymap", properties);

			if("portable bitmap".equals(subtype)) return new MimeType("image", "x-portable-bitmap", properties);
			if("x-portable/bitmap".equals(subtype)) return new MimeType("image", "x-portable-bitmap", properties);
			if("x-pbm".equals(subtype)) return new MimeType("image", "x-portable-bitmap", properties);
			if("pbm".equals(subtype)) return new MimeType("image", "x-portable-bitmap", properties);

			if("x-ico".equals(subtype)) return new MimeType("image", "ico", properties);
			if("x-icon".equals(subtype)) return new MimeType("image", "ico", properties);

			if("x-photoshop".equals(subtype)) return new MimeType("image", "psd", properties);
			if("photoshop".equals(subtype)) return new MimeType("image", "psd", properties);
		}
		else if("zz-application".equals(type)) {
			if("zz-winassoc-psd".equals(subtype)) return new MimeType("image", "psd", properties);
		}
		/*
		
		if("image/x-p".equals(mt)) return "ppm";
		if("image/x-ppm".equals(mt)) return "ppm";
		if("image/ppm".equals(mt)) return "ppm";

		*/
		return new MimeType(type, subtype, properties);
	}
	
	/**
	 * returns a mimetype that match given string
	 * @param strMimeType
	 * @return
	 */
	public static MimeType getInstance(String strMimeType){
		if(strMimeType==null) return ALL;
		strMimeType=strMimeType.trim();
		
		if("*".equals(strMimeType) || strMimeType.length()==0) return ALL;
		String[] arr = ListUtil.listToStringArray(strMimeType, ';');
		if(arr.length==0) return ALL;
		
		String[] arrCT = ListUtil.listToStringArray(arr[0].trim(), '/');
		
		// subtype
		String type=null,subtype=null;
		
		// type
		if(arrCT.length>=1) {
			type=arrCT[0].trim();
			if("*".equals(type)) type=null;
			
			if(arrCT.length>=2) {
				subtype=arrCT[1].trim();
				if("*".equals(subtype)) subtype=null;	
			}
		}
		if(arr.length==1) return getInstance(type,subtype,null);
		

		final Map<String,String> properties=new HashMap<String, String>();
		String entry;
		String[] _arr;
		for(int i=1;i<arr.length;i++){
			entry=arr[i].trim();
			_arr = ListUtil.listToStringArray(entry, '=');
			if(_arr.length>=2)
				properties.put(_arr[0].trim().toLowerCase(), _arr[1].trim());
			else if(_arr.length==1 && !_arr[0].trim().toLowerCase().equals("*"))
				properties.put(_arr[0].trim().toLowerCase(),"");
				
		}
		return getInstance(type,subtype,properties);
	}

	public static MimeType[] getInstances(String strMimeTypes, char delimiter) {
		if(StringUtil.isEmpty(strMimeTypes,true)) return new MimeType[0];
		String[] arr = ListUtil.trimItems(ListUtil.listToStringArray(strMimeTypes, delimiter));
		MimeType[] mtes=new MimeType[arr.length];
		for(int i=0;i<arr.length;i++){
			mtes[i]=getInstance(arr[i]);
		}
		return mtes;
	}




	/**
	 * @return the type
	 */
	public String getType() {
		return type;
	}

	/**
	 * @return the subtype
	 */
	public String getSubtype() {
		return subtype;
	}
	public Map<String, String> getProperties() {
		return properties;
	}
	
	/**
	 * @return the type
	 */
	String getTypeNotNull() {
		return type==null?"*":type;
	}

	/**
	 * @return the subtype
	 */
	String getSubtypeNotNull() {
		return subtype==null?"*":subtype;
	}
	

	public double getQuality() {
		if(q==-1){
			if(properties==null) q=DEFAULT_QUALITY;
			else q= Caster.toDoubleValue(getProperty("q"),DEFAULT_QUALITY);
		}
		return q;
	}

	public Charset getCharset() {
		if(initCS){
			if(properties==null) cs=DEFAULT_CHARSET;
			else {
				String str = getProperty("charset");
				cs= StringUtil.isEmpty(str)?DEFAULT_CHARSET:CharsetUtil.toCharset(str);
			}
			initCS=false;
		}
		return cs;
	}
	
	
	/*
	public int getMxb() {
		return Caster.toIntValue(properties.get("mxb"),DEFAULT_MXB);
	}

	public double getMxt() {
		return Caster.toDoubleValue(properties.get("mxt"),DEFAULT_MXT);
	}*/

	private String getProperty(String name) {
		if(properties!=null) {
		 String value = properties.get(name);
		 if(value!=null)return value;
		 
		 Iterator<Entry<String, String>> it = properties.entrySet().iterator();
		 Entry<String, String> e;
		 while(it.hasNext()){
			 e = it.next();
			 if(name.equalsIgnoreCase(e.getKey())) return e.getValue();
		 }
		}
		return null;
	}



	public boolean hasWildCards() {
		return type==null || subtype==null;
	}

	/**
	 * checks if given mimetype is covered by current mimetype
	 * @param other
	 * @return
	 */
	public boolean match(MimeType other){
		if(this==other) return true;
		if(type!=null && other.type!=null && !type.equals(other.type)) return false;
		if(subtype!=null && other.subtype!=null && !subtype.equals(other.subtype)) return false;
		return true;
	}
	
	public MimeType bestMatch(MimeType[] others){
		MimeType best=null;
		
		for(int i=0;i<others.length;i++){
			if(match(others[i]) && (best==null || best.getQuality()<others[i].getQuality())) {
				best=others[i];
			}
		}
		return best;
	}
	


	/**
	 * checks if other is from the same type, just type and subtype are checked, properties (q,mxb,mxt) are ignored.
	 * @param other
	 * @return
	 */
	public boolean same(MimeType other){
		if(this==other) return true;
		return getTypeNotNull().equals(other.getTypeNotNull()) && getSubtypeNotNull().equals(other.getSubtypeNotNull());
	}
	
	public boolean equals(Object obj){
		if(obj==this) return true;
		
		
		MimeType other;
		if(obj instanceof MimeType)
			other=(MimeType) obj;
		else if(obj instanceof String)
			other=MimeType.getInstance((String)obj);
		else
			return false;
		
		if(!same(other)) return false;
		
		return other.toString().equals(toString());
	}
	
	public String toString(){
		StringBuilder sb=new StringBuilder();
		sb.append(type==null?"*":type);
		sb.append("/");
		sb.append(subtype==null?"*":subtype);
		if(properties!=null){
			String[] keys = properties.keySet().toArray(new String[properties.size()]);
			Arrays.sort(keys);
			//Iterator<Entry<String, String>> it = properties.entrySet().iterator();
			//Entry<String, String> e;
			for(int i=0;i<keys.length;i++){
				sb.append("; ");
				sb.append(keys[i]);
				sb.append("=");
				sb.append(properties.get(keys[i]));
			}
		}
		return sb.toString();
	}



	public static MimeType toMimetype(int format, MimeType defaultValue) {
		switch(format){
		case UDF.RETURN_FORMAT_JSON:return MimeType.APPLICATION_JSON;
		case UDF.RETURN_FORMAT_WDDX:return MimeType.APPLICATION_WDDX;
		case UDF.RETURN_FORMAT_SERIALIZE:return MimeType.APPLICATION_CFML;
		case UDF.RETURN_FORMAT_XML:return MimeType.APPLICATION_XML;
		case UDF.RETURN_FORMAT_PLAIN:return MimeType.APPLICATION_PLAIN;
		case UDFPlus.RETURN_FORMAT_JAVA:return MimeType.APPLICATION_JAVA;
		
		}
		return defaultValue;
	}
	
	

	public static int toFormat(List<MimeType> mimeTypes,int ignore, int defaultValue) {
		if(mimeTypes==null || mimeTypes.size()==0) return defaultValue;
		Iterator<MimeType> it = mimeTypes.iterator();
		int res;
		while(it.hasNext()){
			res=toFormat(it.next(), -1);
			if(res!=-1 && res!=ignore) return res;
		}
		return defaultValue;
	}
	
	public static int toFormat(MimeType mt, int defaultValue) {
		if(mt==null) return defaultValue;
		if(MimeType.APPLICATION_JSON.same(mt)) return  UDF.RETURN_FORMAT_JSON;
		if(MimeType.APPLICATION_WDDX.same(mt)) return  UDF.RETURN_FORMAT_WDDX;
		if(MimeType.APPLICATION_CFML.same(mt)) return  UDF.RETURN_FORMAT_SERIALIZE;
		if(MimeType.APPLICATION_XML.same(mt)) return  UDF.RETURN_FORMAT_XML;
		if(MimeType.APPLICATION_PLAIN.same(mt)) return  UDF.RETURN_FORMAT_PLAIN;
		if(MimeType.APPLICATION_JAVA.same(mt)) return  UDFPlus.RETURN_FORMAT_JAVA;
		return defaultValue;
	}



}
