package page.tools.html;


import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.core.WikiMap;
import org.wikiwebserver.handler.http.FormData;
import org.wikiwebserver.handler.http.HTTPException;
import org.wikiwebserver.handler.http.HTTPHandler;
import org.wikiwebserver.handler.http.interfaces.HTTPResponder;
import page.config.SiteTemplatedPage;
import page.image.CAPTCHAImage;

import org.wikiwebserver.util.ValidationCorrection;
import org.wikiwebserver.util.ValidationError;
import org.wikiwebserver.util.ValidationWarning;

import page.tools.entity.User;

import static org.wikiwebserver.html.HTMLHelper.*;

public abstract class LiveFormPage extends SiteTemplatedPage implements HTTPResponder {
    
    public String getCacheKey() {
        return null;
    }    
    
    public abstract String getFormName();

    
    public abstract void validate(String name, String value) 
        throws ValidationCorrection, ValidationError, ValidationWarning;  
    
    protected abstract void complete(FormData validatedFormData)
        throws HTTPException;
    
    protected abstract void populate() throws HTTPException;    
    
    public void init(HTTPHandler conn) throws HTTPException {
        super.init(conn);

        if (getBrowser() == null) {
            throw new HTTPException(500, "Cookie support required");
        }
        
        this.addResourceRoot("/templates/default/liveform");
        this.addCSSLink("liveform.css");
        
        if (getFormData() == null) {
            populate();
        }
        else {
            String action = getFormData().getFirst("action");
            if (action == null) {
                populate();
            } else if (action.equalsIgnoreCase("clear")) {
                clearInput();
            } else {
                try {
                    validateAll(getFormData());
                    complete(getFormData());        
                } catch (ValidationError ex) {
                    // Form not completed right
                    ex.printStackTrace();
                }
            }            
        }
    }
    
    // Data posted back from a field is stored with the browser or user
    public void ajax() {
        if (getFormData() != null) {
            String name = getFormData().getFirst("name");
            String value = (String) getPostedData();
            if (name != null && value != null) {             
           
                try {
                    // Try auto correction of value
                    try {
                        validate(name, value);
                        // Validation OK, remove warnings and errors
                        clearInputError(name);
                        clearInputWarning(name);                        
                    }
                    catch (ValidationCorrection ex) {
                        clearInputError(name);
                        String correction = ex.getCorrection();
                        if (correction == null) {
                            // Use previous value
                            correction = getInputValue(name);
                        }
                        showInputValue(name, value, correction); 
                        showInputWarning(name, ex.getMessage());                          
                        value = correction;                        
                        // Re-validate after correction  
                        validate(name, value);
                    }
                } catch (ValidationCorrection ex) {
                    showInputValue(name, value, ex.getCorrection());
                } catch (ValidationError ex) {
                    clearInputWarning(name);
                    showInputError(name, ex.getMessage());
                } catch (ValidationWarning ex) {
                    clearInputError(name);
                    showInputWarning(name, ex.getMessage());
                } finally {
                    // Store new value
                    putInputValue(name, value); 
                }
            }
        }
    }
    
    public String liveTextField(String name, String label) {
        return liveTextField(name, label, null);
    }
    
    public String liveTextField(String name, String label, String help) {
        return liveTextField(name, label, true, help);
    }    
    
    public String liveTextField(String name, String label, boolean editable, String help) {
        String value = getInputValue(name);
        String special = editable ? null : "disabled='true'";
        String input = ajaxField(name, value, special, "text");
        return liveInput(name, label, value, null, input, help);
    }   
    
    public String liveCaptcha(String name, String label) {
        return liveCaptcha(name, label, null);
    }
    
    public String liveCaptcha(String name, String label, String help) {
        String value = getInputValue(name);
        String url = WareHouse.getUrlPathForClass(CAPTCHAImage.class);
        String image = image(url, "CAPTCHA Image", "class='captchaImage'");
        String input = ajaxField(name, value, null, "text");
        return liveInput(name, label, value, image, input, help);
    }
    
    public void validateCaptcha(String value) throws ValidationError {
        if (value == null || value.length() == 0)
            throw new ValidationError("Please copy the text in the image above");

        if (!CAPTCHAImage.isCorrectCaptcha(value, getHandler()))
            throw new ValidationError("Text does not match");
    }
    
    public String livePasswordField(String name, String label) {
        return livePasswordField(name, label, null);
    }
    
    public String livePasswordField(String name, String label, String help) {
        String value = getInputValue(name);
        String input = ajaxField(name, value, null, "password");
        return liveInput(name, label, value, null, input, help);
    } 
    
    public String liveCheckbox(String name, String label, String value) {
        return liveCheckbox(name, label, value, null);
    }
    
    public String liveCheckbox(String name, String label, String value, String help) {
        String s = getInputValue(name);
        boolean selected = (s != null) && (s.length() > 0);
        String input = ajaxCheckbox(name, value, selected);
        return liveInput(name, label, value, null, input, help);
    }    
    
    public String liveTextArea(String name, String label) {
        return liveTextArea(name, label, null);
    }
    
    public String liveTextArea(String name, String label, String help) {
        String value = getInputValue(name);
        String input = ajaxTextArea(name, value, null);
        return liveInput(name, label, value, null, input, help);
    }    
    
    public String liveRadioOptions(String name, String label, Collection<String> values) {
        return liveRadioOptions(name, label, values, null);
    }
    
    public String liveRadioOptions(String name, String label, Collection<String> values, String help) {
        String selected = getInputValue(name);
        String input = ajaxRadio(name, values, selected);
        return liveInput(name, label, selected, null, input, help);
    }   
    
    public String liveDropdown(String name, String label, Collection<String> values) {
        return liveDropdown(name, label, values, null);
    }
    
    public String liveDropdown(String name, String label, Collection<String> values, String help) {
        String selected = getInputValue(name);
        String input = ajaxDropdown(name, values, selected);
        return liveInput(name, label, selected, null, input, help);
    }    
    
    public String liveSubmit(String name, String label, String value) {
        return liveSubmit(name, label, value, null);
    }
    
    public String liveSubmit(String name, String label, String value, String help) {
        String input = input(name, name, value, null, "submit");
        return liveInput(name, label, value, null, input, help);
    } 
    
    public String liveSubmit(String submit, String clear) {
        String submitInput = input("action", null, submit, null, "submit");
        String clearInput = input("action", null, clear, null, "submit");
        return liveInput("action", null, null, null, submitInput + clearInput, null);
    }       
    
    private String liveInput(String name, String label, String value, 
                             String image, String input, String help) {

        String error = "";
        String warning = "";
        try {
            validate(name, value);
        }
        catch (ValidationCorrection ex) { } 
        catch (ValidationError ex) { error = ex.getMessage(); } 
        catch (ValidationWarning ex) { warning = ex.getMessage(); }   
        
        String helpBlock = (help == null) ? "" :
                            div(name + "Help", "class='inputHelp'", help);
        
        String imageBlock = (image == null) ? "" :
                             div(name + "ImageBlock", "class='inputImage'", image);        

        String blockClass = (error.length() > 0) ? "inputBlockError" : "inputBlock";
            
        return div(name + "Block", "class='" + blockClass + "'",
                   div(name + "Label", "class='inputLabel'", label) +
                   div(name + "InnerBlock", "class='inputInnerBlock'",
                       helpBlock + imageBlock +
                       div(name + "Input", "class='inputField'", input) +
                       span(name + "Error", "class='inputError'", error) +
                       span(name + "Warning", "class='inputWarning'", warning)
                   ) +
                   div(ContainerType.CLASS, "clear", null)
               );
    }
    
    public String ajaxTextArea(String name, String content, String special) {
        StringBuilder builder = new StringBuilder();

        builder.append("onblur='pov(this);' onkeyup='cpov(this, 1000);'");
        if (special != null) builder.append(" " + special);

        return textarea(name, content, builder.toString());
    }  

    public String ajaxField(String name, String value, String special, String type) {
        StringBuilder builder = new StringBuilder();

        builder.append("onblur='pov(this);' onkeyup='cpov(this, 300);'");
        if (special != null) builder.append(" " + special);

        return input(name, name, value, builder.toString(), type);
    }    
    
    public String ajaxRadio(String name, Collection<String> values, String selected) {
        
        StringBuilder options = new StringBuilder();
        int i = 0;
        for (String value : values) {
            String id = name + (i++);
            String sel =  value.equals(selected) ? " checked='true'" : "";
            String special = "onclick='pov(this);'" + sel;
            options.append(input(name, id, value, special, "radio"));
            options.append("<label for='" + id + "'>" + value + "</label><br/>");
        } 
        
        return options.toString();
    }
    
    public String ajaxDropdown(String name, Collection<String> values, String selected) {
        
       
        String handler = "onchange='pov(this);'";      
        return select(name, values, selected, handler);
    }    
    
    public String ajaxCheckbox(String name, String value, boolean selected) {
        
        StringBuilder options = new StringBuilder();

        String sel =  selected ? " checked='true'" : "";
        String special = "onclick='pov(this);'" + sel;
        options.append(input(name, name, value, special, "checkbox"));
        options.append("<label for='" + name + "'>" + value + "</label><br/>");
        
        return options.toString();
    }    
    
    public String form(String content) {
        return "<form method='post'>" + content + "</form>";
    }
    
    public String getInputValue(String name) {
        String value = null;
        if (getUser() != null) {
            value = (String) getUser().get(name, "LiveFormData", getFormName());
        } else {
            value = (String) getBrowser().get(name, "LiveFormData", getFormName());
        }
        
        try { validate(name, value); }
        catch (ValidationCorrection ex) { value = ex.getCorrection(); } 
        catch (ValidationWarning ex) {}        
        catch (ValidationError ex) {}

        return value;
    }
    
    public String getCurrentInputValue(String name) {
        String value = null;
        if (getUser() != null) {
            value = (String) getUser().get(name, "LiveFormData", getFormName());
        } else {
            value = (String) getBrowser().get(name, "LiveFormData", getFormName());
        }

        return value;
    }    
    
    private Collection<String> getInputNames() {
        User user = getUser();
        if (user != null) {
            return ((WikiMap)getUser().get(getFormName(), "LiveFormData")).keySet(); 
        }
        return ((WikiMap)getBrowser().get(getFormName(), "LiveFormData")).keySet();    
    }
    
    protected void validateAll(FormData formData) throws ValidationError {

        // First set the data
        for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
            String name = entry.getKey();
            String value = entry.getValue().get(0);
            try {
                validate(name, value);
            } catch (ValidationCorrection ex) {
                value = ex.getCorrection();
            } catch (Throwable t) {
                // Skip
            } finally {
                putInputValue(name, value);
            }
        } 
        
        // Now validate fully, throwing exceptions
        for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
            String name = entry.getKey();
            String value = entry.getValue().get(0);
            try {
                validate(name, value);
            } catch (ValidationCorrection ex) {
                value = ex.getCorrection();
            } catch (ValidationWarning ex) {
                // Skip warning
            } finally {
                putInputValue(name, value);
            }
        }
    }       
    
    private void clearInput() {
        Iterator<String> i = getInputNames().iterator();
        while (i.hasNext()) {
            putInputValue(i.next(), null);
        }        
    }
    
    protected void showInputValue(String name, String oldValue, String value) {
        if (value == null) value = "";
        append(updateValueScript(name, oldValue, value)); 
    }   
    
    protected void clearInputError(String name) {
        showInputError(name, null);
        append(updateClassNameScript(name + "Block", "inputBlock")); 
    }    
    
    protected void showInputError(String name, String error) {
        if (error == null) error = "";
        append(updateHTMLScript(name + "Error", error)); 
        append(updateClassNameScript(name + "Block", "inputBlockError")); 
    }
    
    protected void clearInputWarning(String name) {
        showInputWarning(name, null);
    } 
    
    protected void showInputWarning(String name, String warning) {
        if (warning == null) warning = "";
        append(updateHTMLScript(name + "Warning", warning)); 
    }    
    
    protected Object putInputValue(String name, String value) {
        User user = getUser();
        if (user != null) return user.put(name, value, "LiveFormData", getFormName()); 
        return getBrowser().put(name, value, "LiveFormData", getFormName()); 
    }    
}

