/**
 * 
 */
package org.richfaces.renderkit.html;

import java.io.IOException;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSFunctionDefinition;
import org.ajax4jsf.org.w3c.tidy.EntityTable;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.richfaces.component.UIColumn;
import org.richfaces.component.UIDataTable;
import org.richfaces.json.JSONException;
import org.richfaces.json.JSONObject;
import org.richfaces.renderkit.DragIndicatorRendererBase;
import org.richfaces.renderkit.ScriptOptions;

/**
 * Renderer for support drag'n drop for
 * {@link org.richfaces.component.UIExtendedDataTable} component specialized for
 * changing table columns order event.
 * 
 * @author pawelgo
 * 
 */
public class TableDragDropRenderer implements Serializable {

    private static final long serialVersionUID = -8059307641808179967L;

    public final static String DRAG_SOURCE_SCRIPT_ID = "dnd_drag_script";

    public final static String DROP_TARGET_SCRIPT_ID = "dnd_drop_script";

    public final static String DROP_TARGET_BEFORE = "_left";

    public final static String DROP_TARGET_AFTER = "_right";

    private FacesContext context;

    private JSFunctionDefinition onAjaxCompleteFunctionDef;

    private JSFunction preSendAjaxRequestFunction;

	private TableDragDropRenderer(FacesContext context) {
        super();
        this.context = context;
    }

    /**
     * Creates new instance.
     * 
     * @param context
     *            faces context
     * @return instance object
     */
    public static TableDragDropRenderer getInstance(FacesContext context) {
        return new TableDragDropRenderer(context);
    }

    /**
     * Encodes child scripts. It uses
     * {@link org.richfaces.renderkit.DragIndicatorRendererBase} renderer and
     * overwrites marker for DEFAULT drag indicator state. Drag indicator image
     * is the same in DEFAULT and REJECT states.
     * 
     * @param context
     *            faces context
     * @param component
     *            table component
     * @throws IOException
     */
    public void encodeChildScripts(FacesContext context, UIDataTable component)
            throws IOException {
        // new DragIndicatorRendererBase().encodeChildScripts(context,
        // component);
        new DragIndicatorRendererBase() {
            public void encodeChildScripts(FacesContext context,
            		UIDataTable component) throws IOException {
            	
            	ResponseWriter responseWriter = context.getResponseWriter();
            	responseWriter.write("\nvar elt = $(\""+ component.getBaseClientId(context) +":dataTable_indicator\");\n");
            	responseWriter.write("elt.markers = {};\n");
            	responseWriter.write("elt.indicatorTemplates = {};\n");
            	
                super.encodeChildScripts(context, component);
                // redefine DEFAULT marker to be like REJECT marker
                
                responseWriter.write("elt.markers['" + DEFAULT + "'] = \"");
                responseWriter.write(getPredefinedMarker(context, REJECT));
                responseWriter.write("\";\n");
                responseWriter.write("createExtDragIndicator(elt, '', '');\n");
                
            }
        }.encodeChildScripts(context, component);
    }

    /**
     * Renders support for drag operation specialized for changing table columns
     * order event.
     * 
     * @param column
     *            table column
     * @param dragSourceId
     *            drag source HTML element id
     * @param indicatorId
     *            drag indicator id
     * @param dragLabel
     *            label to be displayed in indicator
     * @throws IOException
     */
    public void renderDragSupport(UIColumn column, String dragSourceId,
            String indicatorId, String dragLabel) throws IOException {
    	String varName = "DnD_ExtSimpleDraggable_"+column.getId().replaceAll("[^A-Za-z0-9_]", "_");
        StringBuffer buffer = new StringBuffer("delete " + varName + ";\nvar " + varName + " = ");
        JSFunction function = new JSFunction("new DnD.ExtSimpleDraggable");
        function.addParameter(dragSourceId);
        String dragSourceScriptId = column.getClientId(context) + ":"
                + DRAG_SOURCE_SCRIPT_ID;
        ExtDraggableRendererContributor contributor = ExtDraggableRendererContributor
                .getInstance();
        ScriptOptions dragOptions = contributor.buildOptions(context, column,
                dragSourceScriptId, indicatorId);

        JSONObject dndParams = new JSONObject();
        try {
            dndParams.put("label", dragLabel == null ? "" : dragLabel);
        } catch (JSONException e) {
        }
        dragOptions.addOption("dndParams", dndParams.toString());

        function.addParameter(dragOptions);
        function.appendScript(buffer);

        String scriptContribution = contributor.getScriptContribution(context,
                column);
        if (scriptContribution != null && scriptContribution.length() != 0) {
            buffer.append(scriptContribution);
        }

        ResponseWriter writer = context.getResponseWriter();

        writer.startElement(HTML.SCRIPT_ELEM, column);
        writer.writeAttribute("id", dragSourceScriptId, "id");
        writer.write(escapeHtmlEntities(buffer));
        writer.endElement(HTML.SCRIPT_ELEM);
    }// renderDragSupport

    /**
     * Renders support for drop operation specialized for changing table columns
     * order event.
     * 
     * @param column
     *            table column
     * @param dropTargetId
     *            drop target HTML element id
     * @param before
     *            true if target is positioned before column
     * @throws IOException
     */
    public void renderDropSupport(UIColumn column, String dropTargetId,
            boolean before) throws IOException {
        // RendererContributor contributor =
        // DropzoneRendererContributor.getInstance();
        ExtDropzoneRendererContributor contributor = ExtDropzoneRendererContributor
                .getInstance();
        String varName = "DnD_ExtSimpleDropZone_"+column.getId().replaceAll("[^A-Za-z0-9_]", "_") + (before?"L":"R");
        StringBuffer buffer = new StringBuffer("delete " + varName + ";\nvar " + varName + " = ");
        JSFunction function = new JSFunction("new DnD.ExtSimpleDropZone");
        function.addParameter(dropTargetId);
        ScriptOptions dropOptions = contributor.buildOptions(context, column);
        JSONObject dndParams = new JSONObject();
        dropOptions.addOption("dndParams", dndParams.toString());

        function.addParameter(dropOptions);
        function.appendScript(buffer);

        String dropTargetScriptId = column.getClientId(context) + ":"
                + DROP_TARGET_SCRIPT_ID
                + (before ? DROP_TARGET_BEFORE : DROP_TARGET_AFTER);
        String scriptContribution = contributor.getScriptContribution(context,
                column, dropTargetScriptId, preSendAjaxRequestFunction,
                onAjaxCompleteFunctionDef);
        if (scriptContribution != null && scriptContribution.length() != 0) {
            buffer.append(scriptContribution);
        }

        ResponseWriter writer = context.getResponseWriter();

        writer.startElement(HTML.SCRIPT_ELEM, column);
        writer.writeAttribute("id", dropTargetScriptId, "id");
        writer.write(escapeHtmlEntities(buffer));
        writer.endElement(HTML.SCRIPT_ELEM);
    }// renderDropSupport

    /**
     * Help method for escaping HTML entities.
     * 
     * @param orig
     *            string to escape
     * @return string with escaped HTML entities
     */
    protected String escapeHtmlEntities(CharSequence orig) {
        StringBuffer buff = new StringBuffer(orig);
        EntityTable defaultEntityTable = EntityTable.getDefaultEntityTable();
        Matcher matcher = Pattern.compile("\\&\\w+\\;").matcher(orig);
        int delta = 0;
        while (matcher.find()) {
            String name = matcher.group().substring(0,
                    matcher.group().length() - 1);
            int code = defaultEntityTable.entityCode(name);
            if (0 != code) {
                String replacement = "&#" + code + ";";
                buff.replace(matcher.start() - delta, matcher.end() - delta,
                        replacement);
                delta = delta + matcher.group().length() - replacement.length();
            }
        }
        return buff.toString();
    }

    public JSFunctionDefinition getOnAjaxCompleteFunctionDef() {
        return onAjaxCompleteFunctionDef;
    }

    /**
     * Set JavaScript function to be called on AJAX request complete.
     * 
     * @param onAjaxCompleteFunction
     *            JavaScriot function to set
     */
    public void setOnAjaxCompleteFunctionDef(
            JSFunctionDefinition onAjaxCompleteFunctionDef) {
        this.onAjaxCompleteFunctionDef = onAjaxCompleteFunctionDef;
    }

    public void setPreSendAjaxRequestFunction(
            JSFunction preSendAjaxRequestFunction) {
        this.preSendAjaxRequestFunction = preSendAjaxRequestFunction;
    }

}
