/**
 * License Agreement.
 *
 *  JBoss RichFaces - Ajax4jsf Component Library
 *
 * Copyright (C) 2007  Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */
package org.richfaces.renderkit;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIParameter;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.ConverterException;

import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.resource.InternetResource;
import org.ajax4jsf.resource.util.URLToStreamHelper;
import org.ajax4jsf.util.InputUtils;
import org.richfaces.component.UIEditor;

/**
 * Editor component renderer base class.
 * 
 * @author Alexandr Levkovsky
 *
 */
public class EditorRendererBase extends InputRendererBase {
	
	/** Specific script resource name which will be used to get server suffix */
	private final static String SPECIFIC_SCRIPT_RESOURCE_NAME = "org/richfaces/renderkit/html/1$1.js";
	/** Specific xcss resource name which will be used to get server suffix */
	private final static String SPECIFIC_XCSS_RESOURCE_NAME = "org/richfaces/renderkit/html/1$1.xcss";

	/** Editor viewMode attribute value which should disable rendering tinyMCE initialization */
	private final static String TINY_MCE_DISABLED_MODE = "source";
		
	/* (non-Javadoc)
	 * @see org.richfaces.renderkit.InputRendererBase#getComponentClass()
	 */
	@Override
	protected Class<? extends UIComponent> getComponentClass() {
		return UIEditor.class;
	}

	/* (non-Javadoc)
	 * @see org.richfaces.renderkit.InputRendererBase#getConvertedValue(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object)
	 */
	@Override
	public Object getConvertedValue(FacesContext context,
			UIComponent component, Object submittedValue)
			throws ConverterException {
		return InputUtils.getConvertedValue(context, component, submittedValue);
	}

	/* (non-Javadoc)
	 * @see org.richfaces.renderkit.InputRendererBase#doDecode(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected void doDecode(FacesContext context, UIComponent component) {
		String clientId = component.getClientId(context) + UIEditor.EDITOR_TEXT_AREA_ID_SUFFIX;
        Map requestParameterMap = context.getExternalContext().getRequestParameterMap();
        String newValue = (String) requestParameterMap.get(clientId);
        if (null != newValue) {
    		UIInput input = (UIInput) component;
            input.setSubmittedValue(newValue);
        }
	}
	
	
	/**
	 * Method to get converted to String model value or if validation not passed submitted value for component
	 * 
	 * @param context - faces context instance
	 * @param component - component for which method is applied 
	 * @return converted to String component value
	 */
	protected String getFormattedComponentStringValue(FacesContext context,
			UIEditor component) {
		String fieldValue = (String) component.getSubmittedValue();
		if (fieldValue == null) {
			fieldValue = InputUtils.getConvertedStringValue(context, component,
					component.getValue());
		}
		return fieldValue;
	}
	
	/**
	 * Method to get exact script resource URI suffix
	 * 
	 * @param context - faces context instance
	 * @return string with script resource URI suffix
	 */
	protected String getSriptMappingSuffix(FacesContext context) {
		return getResourceSuffix(context, SPECIFIC_SCRIPT_RESOURCE_NAME);
	}
	
	/**
	 * Method to get exact xcss resource URI suffix
	 * 
	 * @param context - faces context instance
	 * @return string with xcss resource URI suffix
	 */
	protected String getCssMappingSuffix(FacesContext context) {
		return getResourceSuffix(context, SPECIFIC_XCSS_RESOURCE_NAME);
	}
	
	/**
	 * Method to get resource URI suffix which was added due to web.xml mappings
	 * 
	 * @param context - faces context instance
	 * @param resourceName - name of the resource which should be checked  
	 * @return string with resource URI suffix which was added after resource name
	 */
	private String getResourceSuffix(FacesContext context, String resourceName){
		InternetResource resource = getResource(resourceName);
		String resourceUri = resource.getUri(context, null);
		String suffix = resourceUri.substring(resourceUri.indexOf(resourceName) + resourceName.length());
		return suffix;
	}

	/**
	 * Method to write tinyMCE configuration script parameters from property
	 * file if it was determined through Editor configuration attribute.
	 * 
	 * @param context - faces context instance
	 * @param component - Editor component instance
	 * @throws IOException
	 */
	public void writeEditorConfigurationParameters(FacesContext context,
			UIEditor component) throws IOException {
		ResponseWriter writer = context.getResponseWriter();
		writer.writeText("var tinyMceParams = ", null);

		String parametersConfigName = component.getConfiguration();
		if (parametersConfigName != null && parametersConfigName.length() > 0) {
			loadPropertiesAndWrite(context, parametersConfigName, writer, false);
		} else {
			writer.writeText("{};\n", null);
		}
	}
	
	/**
	 * Method to write custom plugins script parameters from property
	 * file if it was determined through Editor configuration attribute.
	 * 
	 * @param context - faces context instance
	 * @param component - Editor component instance
	 * @throws IOException
	 */
	//XXX nick - merge with writeEditorConfigurationParameters() method
	public void writeEditorCustomPluginsParameters(FacesContext context,
			UIEditor component) throws IOException {
		ResponseWriter writer = context.getResponseWriter();

		String customPluginsConfigName = component.getCustomPlugins();
		if (customPluginsConfigName != null && customPluginsConfigName.length() > 0) {
			loadPropertiesAndWrite(context, customPluginsConfigName, writer, true);
		}
	}
	
	/**
	 * Method to load configuration parameters from property file and write them
	 * @param context
	 * @param configName
	 * @param writer
	 * @param cutomPlugins
	 */
	private void loadPropertiesAndWrite(FacesContext context,
			String configName, ResponseWriter writer, boolean cutomPlugins) {
		Properties parameters = new Properties();
		try {
			ClassLoader loader = Thread.currentThread().getContextClassLoader();

			// XXX nick - use org.ajax4jsf.resource.util.URLToStreamHelper.urlToStream(URL)
			URL url = loader.getResource(configName + ".properties");
			InputStream is = URLToStreamHelper.urlToStream(url);
			if (is == null) {
				throw new FacesException(
						"Editor configuration properties file with name '"
								+ configName + "' was not found in class path");
			}

			// XXX nick - streams should be closed, in finally block
			try {
				parameters.load(is);
			} catch (Exception e) {
				throw new FacesException(e);
			} finally {
				is.close();
			}
			writer.writeText("\n", null);
			if (cutomPlugins) {
				writer.writeText(
						this.getCustomPluginsCode(context, parameters), null);
			} else {
				writer.writeText(this.convertProperties(parameters), null);
			}
			writer.writeText(";\n", null);
		} catch (IOException e) {
			throw new FacesException(e);
		}

	}

	/**
	 * Method to build string with parameters from property file
	 * 
	 * @param map - map with properties
	 * @return string with parameters in valid form for script
	 */
	@SuppressWarnings("unchecked")
	private String convertProperties(Map map) {
		StringBuilder ret = new StringBuilder("{");
		boolean first = true;
		for (Iterator<Map.Entry<Object, Object>> iter = map.entrySet()
				.iterator(); iter.hasNext();) {
			Map.Entry<Object, Object> entry = iter.next();
			if (!first) {
				ret.append(",\n");
			}

			ret.append(entry.getKey());
			ret.append(":");
			ret.append(entry.getValue());
			first = false;
		}
		return ret.append("} ").toString();
	}
	
	/**
	 * Method to write script for loading custom plugins
	 * 
	 * @param context - faces context instance
	 * @param map - properties map
	 * @return string with script
	 */
	@SuppressWarnings("unchecked")
	private String getCustomPluginsCode(FacesContext context, Map map) {
		StringBuilder ret = new StringBuilder();
		for (Iterator<Map.Entry<Object, Object>> iter = map.entrySet()
				.iterator(); iter.hasNext();) {
			Map.Entry<Object, Object> entry = iter.next();

			ret.append("tinymce.PluginManager.load('");
			ret.append(entry.getKey());
			ret.append("','");
			ret.append(context.getExternalContext().getRequestContextPath()+entry.getValue());
			ret.append("',null,{richfaces:true});");
		}
		return ret.toString();
	}	
	
	/**
	 * Method to write tinyMCE configuration script parameters from Editor component attributes.
	 * 
	 * @param context - faces context instance
	 * @param component - Editor component instance
	 * @throws IOException
	 */
	public void writeEditorConfigurationAttributes(FacesContext context,
			UIEditor component) throws IOException {
		ResponseWriter writer = context.getResponseWriter();

		String theme = component.getTheme();
		String language = component.getLanguage();
		String plugins = component.getPlugins();
		String oninit = component.getOninit();
		String onsave = component.getOnsave();
		String onchange = component.getOnchange();
		String onsetup = component.getOnsetup();
		String dialogType = component.getDialogType();
		String skin = component.getSkin();
		Integer width = component.getWidth();
		Integer height = component.getHeight();

		if (theme != null && theme.length() > 0) {
			writer.writeText("tinyMceParams.theme = "
					+ ScriptUtils.toScript(theme) + ";\n", null);
		}
		if (language != null && language.length() > 0) {
			writer.writeText("tinyMceParams.language = "
					+ ScriptUtils.toScript(language) + ";\n", null);
		}
		writer.writeText("tinyMceParams.auto_resize = "
				+ ScriptUtils.toScript(component.isAutoResize()) + ";\n", null);

		writer.writeText("tinyMceParams.readonly = "
				+ ScriptUtils.toScript(component.isReadonly()) + ";\n", null);

		if (plugins != null && plugins.length() > 0) {
			writer.writeText("tinyMceParams.plugins = "
					+ ScriptUtils.toScript(plugins) + ";\n", null);
		}
		if (width != null) {
			writer.writeText("tinyMceParams.width = "
					+ ScriptUtils.toScript(width) + ";\n", null);
		}
		if (height != null) {
			writer.writeText("tinyMceParams.height = "
					+ ScriptUtils.toScript(height) + ";\n", null);
		}

		// XXX nick - use local variables
		if (oninit != null && oninit.length() > 0) {
			writer.writeText("tinyMceParams.oninit = function (event) {\n"
					+ oninit + "\n" + "};\n", null);
		}
		if (onsave != null && onsave.length() > 0) {
			writer
					.writeText(
							"tinyMceParams.save_callback = function (event, element_id, html, body) {\n		return "
									+ onsave + "\n" + "};\n", null);
		}
		if (onchange != null && onchange.length() > 0) {
			writer.writeText(
					"tinyMceParams.onchange_callback = function (event, inst) {\n"
							+ onchange + "\n" + "};\n", null);
		}
		if (onsetup != null && onsetup.length() > 0) {
			writer.writeText("tinyMceParams.setup = function (event, ed) {\n"
					+ onsetup + "\n" + "};\n", null);

		}
		if (dialogType != null && dialogType.length() > 0) {
			writer.writeText("tinyMceParams.dialog_type = "
					+ ScriptUtils.toScript(dialogType) + ";\n", null);
		}
		if (skin != null && skin.length() > 0) {
			writer.writeText("tinyMceParams.skin = "
					+ ScriptUtils.toScript(skin) + ";\n", null);
		} else {
			writer.writeText("if(!tinyMceParams.skin){\n", null);
			writer.writeText("	tinyMceParams.skin = 'richfaces';\n", null);
			writer.writeText("}\n", null);
		}

		writer.writeText("if(tinyMceParams.strict_loading_mode == null){\n",
				null);
		writer.writeText("	tinyMceParams.strict_loading_mode = true;\n", null);
		writer.writeText("}\n", null);
	}

	/**
	 * Method to write tinyMCE configuration script parameters which was defined
	 * as <f:param> children for Editor
	 * 
	 * @param context -
	 *            faces context instance
	 * @param component -
	 *            Editor component instance
	 * @throws IOException
	 */
	public void writeEditorParameters(FacesContext context,
			UIComponent component) throws IOException {

		ResponseWriter writer = context.getResponseWriter();
		List<UIComponent> children = component.getChildren();
		for (UIComponent child : children) {
			if (child instanceof UIParameter) {
				UIParameter parameter = (UIParameter) child;
				StringBuilder b = new StringBuilder();
				b.append("tinyMceParams.");
				ScriptUtils.addEncoded(b, parameter.getName());
				b.append(" = ");
				if (parameter.getValue() != null
						&& (parameter.getValue().equals("true") || parameter
								.getValue().equals("false"))) {
					ScriptUtils.addEncoded(b, parameter.getValue());
				} else {
					b.append(ScriptUtils.toScript(parameter.getValue()));
				}
				b.append(";\n");
				writer.writeText(b.toString(), null);
			}
		}
	}

	/**
	 * Method to check if rendering of tinyMCE scripts needed. 
	 * 
	 * @param component - Editor component instance
	 * @return true if needed or false if only target textarea should be rendered 
	 */
	public boolean shouldRenderTinyMCE(UIEditor component) {
		//XXX nick - use TINY_MCE_DISABLED_MODE.equalsIgnoreCase(component.getViewMode())
		if (TINY_MCE_DISABLED_MODE.equalsIgnoreCase(component.getViewMode())) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Method to get target textarea style if width or height attributes was determined
	 * 
	 * @param component - Editor component instance
	 * @return string with style properties
	 */
	public String getTextAreaStyle(UIEditor component) {
		StringBuilder b = new StringBuilder();
		if (component.getWidth() != null) {
			b.append("width: " + component.getWidth() + "px;");
		}
		if (component.getHeight() != null) {
			b.append("height: " + component.getHeight() + "px;");
		}
		return b.toString();
	}
	
	
}
