/*
 * Copyright (C) 2003-2008 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.ecm.webui.utils;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyType;

import org.exoplatform.commons.utils.IOUtil;
import org.exoplatform.ecm.utils.text.Text;
import org.exoplatform.ecm.webui.form.UIFormUploadInputNoUploadButton;
import org.exoplatform.ecm.webui.form.validator.CategoryValidator;
import org.exoplatform.ecm.webui.form.validator.CronExpressionValidator;
import org.exoplatform.ecm.webui.form.validator.DateValidator;
import org.exoplatform.ecm.webui.form.validator.ECMNameValidator;
import org.exoplatform.ecm.webui.form.validator.RepeatCountValidator;
import org.exoplatform.ecm.webui.form.validator.RepeatIntervalValidator;
import org.exoplatform.services.cms.JcrInputProperty;
import org.exoplatform.upload.UploadResource;
import org.exoplatform.wcm.webui.Utils;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.form.UIFormCheckBoxInput;
import org.exoplatform.webui.form.UIFormDateTimeInput;
import org.exoplatform.webui.form.UIFormInputBase;
import org.exoplatform.webui.form.UIFormMultiValueInputSet;
import org.exoplatform.webui.form.UIFormSelectBox;
import org.exoplatform.webui.form.UIFormUploadInput;
import org.exoplatform.webui.form.validator.DateTimeValidator;
import org.exoplatform.webui.form.validator.EmailAddressValidator;
import org.exoplatform.webui.form.validator.MandatoryValidator;
import org.exoplatform.webui.form.validator.NullFieldValidator;
import org.exoplatform.webui.form.validator.NumberFormatValidator;
import org.exoplatform.webui.form.validator.StringLengthValidator;

/*
 * Created by The eXo Platform SAS
 * @author : Hoa.Pham
 *          hoa.pham@exoplatform.com
 * Jun 23, 2008
 */
/**
 * The Class DialogFormUtil.
 */
public class DialogFormUtil {

  public static String VALIDATOR_PARAM_BEGIN      ="(";
  public static String VALIDATOR_PARAM_END        =")";
  public static String VALIDATOR_PARAM_SEPERATOR  =";";
  public static String SANITIZATION_FLAG          ="noSanitization";

  /** Type of parameters which were passed for the validator
   * TODO: Please add all the possible type here and parser it in side the
   * function parserValidatorParam.
   * If any question, ask for VinhNT from Content's team.
   * */
  public static String TYPE_FLOAT                 ="Float";
  public static String TYPE_DOUBLE                ="Double";
  public static String TYPE_INTEGER               ="Int";
  public static String TYPE_STRING                ="String";
  /**
   * Prepare map.
   *
   * @param inputs the inputs
   * @param properties the properties
   * @return the map< string, jcr input property>
   * @throws Exception the exception
   */
  @SuppressWarnings("unchecked")
  public static Map<String, JcrInputProperty> prepareMap(List inputs, Map properties) throws Exception {
    return prepareMap(inputs, properties, null);
  }
  @SuppressWarnings("unchecked")
  public static Map<String, JcrInputProperty> prepareMap(List inputs, Map properties, Map options) throws Exception {
    Map<String, String> changeInJcrPathParamMap = new HashMap<String, String>();
    Map<String, JcrInputProperty> rawinputs = new HashMap<String, JcrInputProperty>();
    HashMap<String, JcrInputProperty> hasMap = new HashMap<String, JcrInputProperty>() ;
    String inputName = null;
    String mimeTypeJcrPath = null;
    InputStream inputStream = null;
    byte[] content = null;
    Map<String, JcrInputProperty> mimeTypes = new HashMap<String, JcrInputProperty>();
    for (int i = 0; i < inputs.size(); i++) {
      JcrInputProperty property = null;
      String option = null;
      if(inputs.get(i) instanceof UIFormMultiValueInputSet) {
        UIFormMultiValueInputSet inputI = (UIFormMultiValueInputSet)inputs.get(i);
        inputName = (inputI).getName() ;
        if(!hasMap.containsKey(inputName)) {
          List<UIComponent> inputChild = inputI.getChildren();
          property = (JcrInputProperty) properties.get(inputName);          
          if (inputChild != null && inputChild.size() > 0 && 
              inputChild.get(0) instanceof UIFormUploadInput) {
            Map<String, List> uploadDataMap = new TreeMap<String, List>();
            for (UIComponent child : inputChild) {
              UIFormUploadInput uploadInput = (UIFormUploadInput)child;
              String uploadDataName = null;
              String uploadMimeType = null;
              byte[] uploadData = null;
              if (uploadInput instanceof UIFormUploadInputNoUploadButton) {
                uploadDataName = ((UIFormUploadInputNoUploadButton)uploadInput).getFileName();
                uploadMimeType = ((UIFormUploadInputNoUploadButton)uploadInput).getMimeType();
                uploadData = ((UIFormUploadInputNoUploadButton)uploadInput).getByteValue();
              } else {
                UploadResource uploadResource = (uploadInput).getUploadResource();
                if (uploadResource != null) {
                String location = uploadResource.getStoreLocation();
                uploadDataName = uploadResource.getFileName();
                uploadData = IOUtil.getFileContentAsBytes(location);
                uploadMimeType = uploadResource.getMimeType();
                }
              }
              if (uploadDataName != null && uploadData != null) {
                List data = new ArrayList();
                data.add(uploadMimeType);
                data.add(uploadData);
                if (!uploadDataMap.containsKey(uploadDataName)) {
                  uploadDataMap.put(uploadDataName, data);
                } else {
                  int count = 1;
                  while (uploadDataMap.containsKey(uploadDataName + count)) {
                    count ++;
                  }
                  uploadDataMap.put(uploadDataName + count, data);
                }
              }
//                mimeTypeJcrPath = property.getJcrPath().replace("jcr:data", "jcr:mimeType");
//                JcrInputProperty mimeTypeInputPropertyTmp = new JcrInputProperty();
//                mimeTypeInputPropertyTmp.setJcrPath(mimeTypeJcrPath);
//                mimeTypeInputPropertyTmp.setValue(((UIFormUploadInput) input).getUploadResource().getMimeType());
//                mimeTypes.put(mimeTypeJcrPath, mimeTypeInputPropertyTmp);              
            }
            property.setValue(uploadDataMap);            
          } else {
            List<String> values = (List<String>) (inputI).getValue() ;
            if(property != null){
              property.setValue(values.toArray(new String[values.size()])) ;
            }
          }
        }
        hasMap.put(inputName, property) ;
      } else {
        UIFormInputBase input = (UIFormInputBase) inputs.get(i);
        property = (JcrInputProperty) properties.get(input.getName());
        if(options != null && options.get(input.getName()) != null) option = (String)options.get(input.getName());
        if(property != null) {
          if (input instanceof UIFormUploadInput) {
            UploadResource uploadResource = ((UIFormUploadInput) input).getUploadResource();
            if (uploadResource == null) { 
              if (property.getChangeInJcrPathParam() != null)
                changeInJcrPathParamMap.put(property.getChangeInJcrPathParam(), "");
              continue;
            }
              String location = uploadResource.getStoreLocation();
              byte[] uploadData = IOUtil.getFileContentAsBytes(location);
              property.setValue(uploadData);
              //change param in jcr path
              if (property.getChangeInJcrPathParam() != null) 
                changeInJcrPathParamMap.put(property.getChangeInJcrPathParam(), 
                                            Text.escapeIllegalJcrChars(uploadResource.getFileName()));

              mimeTypeJcrPath = property.getJcrPath().replace("jcr:data", "jcr:mimeType");
              JcrInputProperty mimeTypeInputPropertyTmp = new JcrInputProperty();
              mimeTypeInputPropertyTmp.setJcrPath(mimeTypeJcrPath);
              mimeTypeInputPropertyTmp.setValue(((UIFormUploadInput) input).getUploadResource().getMimeType());
              mimeTypes.put(mimeTypeJcrPath, mimeTypeInputPropertyTmp);
          } else if(input instanceof UIFormDateTimeInput) {
            property.setValue(((UIFormDateTimeInput)input).getCalendar()) ;
          } else if(input instanceof UIFormSelectBox) {
            UIFormSelectBox uiSelectBox = (UIFormSelectBox) input;
            if(!uiSelectBox.isMultiple()) {
              property.setValue(uiSelectBox.getValue());
            }else {
              property.setValue(uiSelectBox.getSelectedValues());
            }
          } else if(input instanceof UIFormCheckBoxInput) {
            property.setValue(((UIFormCheckBoxInput)input).isChecked()) ;
          } else {
            if(input.getValue()!=null) {
              String inputValue = input.getValue().toString().trim();
              boolean isEmpty = Utils.isEmptyContent(inputValue);
              if(isEmpty) inputValue = "";
              else if(option == null || option.indexOf(SANITIZATION_FLAG) < 0) inputValue = Utils.sanitize(inputValue);
              property.setValue(inputValue);
            } else {
              property.setValue(input.getValue());
            }
          }
        }
      }
    }
    Iterator iter = properties.values().iterator() ;
    JcrInputProperty property ;
    while (iter.hasNext()) {
      property = (JcrInputProperty) iter.next() ;
      rawinputs.put(property.getJcrPath(), property) ;
    }
    for (String jcrPath : mimeTypes.keySet()) {
      if (!rawinputs.containsKey(jcrPath)) {
        rawinputs.put(jcrPath, mimeTypes.get(jcrPath)) ;
      }
    }
    List<UIFormUploadInput> formUploadList = new ArrayList<UIFormUploadInput>();
    for (Object input : inputs) {
      if (input instanceof UIFormMultiValueInputSet) {
        UIFormMultiValueInputSet uiSet = (UIFormMultiValueInputSet) input;
        if (uiSet.getId() != null && uiSet.getUIFormInputBase().equals(UIFormUploadInput.class)
            && uiSet.getId().equals("attachment__")) {
          List<UIComponent> list = uiSet.getChildren();
          for (UIComponent component : list) {
            if (!formUploadList.contains(component)) formUploadList.add((UIFormUploadInput) component);
          }
        }
      } else if (input instanceof UIFormUploadInput) {
        if (!formUploadList.contains(input)) formUploadList.add((UIFormUploadInput) input);
      }
    }
    if (formUploadList.size() > 0) {
      List<String> keyListToRemove = new ArrayList<String>();
      Map<String, JcrInputProperty> jcrPropertiesToAdd = new HashMap<String, JcrInputProperty>();
      for (Object inputJCRKeyTmp : rawinputs.keySet()) {
        String inputJCRKey = (String) inputJCRKeyTmp;
        if (inputJCRKey.contains("attachment__")) {
          JcrInputProperty jcrInputProperty = rawinputs.get(inputJCRKey);
          for (UIFormUploadInput formUploadInput : formUploadList) {
            JcrInputProperty newJcrInputProperty = clone(jcrInputProperty);
            if (formUploadInput == null || formUploadInput.getUploadResource() == null
                || formUploadInput.getUploadResource().getFileName() == null)
              continue;
            String fileName = formUploadInput.getUploadResource().getFileName();
            String newJCRPath = inputJCRKey.replace("attachment__", fileName);
            newJcrInputProperty.setJcrPath(newJCRPath);
            if (inputJCRKey.endsWith("attachment__")) {
              newJcrInputProperty.setValue(fileName);
              JcrInputProperty mimeTypeInputPropertyTmp = new JcrInputProperty();
              mimeTypeInputPropertyTmp.setJcrPath(newJCRPath + "/jcr:content/jcr:mimeType");
              mimeTypeInputPropertyTmp.setValue(formUploadInput.getUploadResource().getMimeType());
              jcrPropertiesToAdd.put(mimeTypeInputPropertyTmp.getJcrPath(), mimeTypeInputPropertyTmp);
            }
            if (inputJCRKey.endsWith("jcr:data")) {
              inputStream = formUploadInput.getUploadDataAsStream();
              newJcrInputProperty.setValue(inputStream);
            }
            jcrPropertiesToAdd.put(newJCRPath, newJcrInputProperty);
          }
          keyListToRemove.add(inputJCRKey);
          keyListToRemove.add(inputJCRKey.replace("jcr:data", "jcr:mimeType"));
        }
      }
      for (String keyToRemove : keyListToRemove) {
        rawinputs.remove(keyToRemove);
      }
      rawinputs.putAll(jcrPropertiesToAdd);
    }

    if (changeInJcrPathParamMap.isEmpty()) {
      return rawinputs;
    }
    
    Map<String, JcrInputProperty> ret = new HashMap<String, JcrInputProperty>();
    Set<String> removedKeys = new HashSet<String>();
    for (Map.Entry<String, String> changeEntry : changeInJcrPathParamMap.entrySet()) {
      for (Map.Entry<String, JcrInputProperty> entry : rawinputs.entrySet()) {
        if (entry.getKey().contains(changeEntry.getKey())) {
          removedKeys.add(entry.getKey());
          JcrInputProperty value = entry.getValue();
          if (value.getJcrPath() != null) {
            value.setJcrPath(value.getJcrPath().replace(changeEntry.getKey(), changeEntry.getValue()));
          }
          if (value.getValue() != null && value.getValue() instanceof String) {
            value.setValue(((String)value.getValue()).replace(changeEntry.getKey(), changeEntry.getValue()));
          }
          if (value != null && !"".equals(value) && changeEntry.getValue() != null && !"".equals(changeEntry.getValue())) {
            ret.put(entry.getKey().replace(changeEntry.getKey(), changeEntry.getValue()), 
                    value);
          }
        }
      }
    }
    for (Map.Entry<String, JcrInputProperty> entry : rawinputs.entrySet()) {
      if (!removedKeys.contains(entry.getKey())) {
        ret.put(entry.getKey(), entry.getValue());
      }
    }
    
    return ret;
  }

  private static JcrInputProperty clone(JcrInputProperty fileNodeInputProperty) {
    JcrInputProperty jcrInputProperty = new JcrInputProperty();
    jcrInputProperty.setJcrPath(fileNodeInputProperty.getJcrPath());
    jcrInputProperty.setMixintype(fileNodeInputProperty.getMixintype());
    jcrInputProperty.setNodetype(fileNodeInputProperty.getNodetype());
    jcrInputProperty.setType(fileNodeInputProperty.getType());
    jcrInputProperty.setValue(fileNodeInputProperty.getValue());
    jcrInputProperty.setValueType(fileNodeInputProperty.getValueType());
    return jcrInputProperty;
  }

  /**
   * Creates the form input.
   *
   * @param type the type
   * @param name the name
   * @param label the label
   * @param validateType the validate type
   * @param valueType the value type
   * @return the t
   * @throws Exception the exception
   */
  @SuppressWarnings("unchecked")
  public static <T extends UIFormInputBase> T createFormInput(Class<T> type,String name, String label,
      String validateType, Class valueType) throws Exception {
    Object[] args= {name, null, valueType };
    UIFormInputBase formInput = type.getConstructor().newInstance(args) ;
    addValidators(formInput, validateType);
    if(label != null && label.length()!=0) {
      formInput.setLabel(label);
    }
    return type.cast(formInput);
  }

  /**
   * Gets the property value as string.
   *
   * @param node the node
   * @param propertyName the property name
   * @return the property value as string
   * @throws Exception the exception
   */
  public static String getPropertyValueAsString(Node node, String propertyName) throws Exception {
    Property property = null;
    try{
      property = node.getProperty(propertyName);
    }catch (PathNotFoundException e) {
      return "";
    }
    int valueType = property.getType() ;
    switch(valueType) {
    case PropertyType.STRING: //String
      return property.getString() ;
    case PropertyType.LONG: // Long
      return Long.toString(property.getLong()) ;
    case PropertyType.DOUBLE: // Double
      return Double.toString(property.getDouble()) ;
    case PropertyType.DATE: //Date
      return property.getDate().getTime().toString() ;
    case PropertyType.BOOLEAN: //Boolean
      return Boolean.toString(property.getBoolean()) ;
    case PropertyType.NAME: //Name
      return property.getName() ;
    case 8: //Path
    case 9: //References
    case 0: //Undifine
    }
    return "" ;
  }

  public static Class getValidator(String validatorType) throws ClassNotFoundException {
    if(validatorType.equals("name")) {
      return ECMNameValidator.class ;
    } else if (validatorType.equals("email")){
      return EmailAddressValidator.class ;
    } else if (validatorType.equals("number")) {
      return NumberFormatValidator.class;
    } else if (validatorType.equals("empty")){
      return MandatoryValidator.class ;
    } else if(validatorType.equals("null")) {
      return NullFieldValidator.class;
    } else if(validatorType.equals("datetime")) {
      return DateTimeValidator.class;
    } else if(validatorType.equals("date")) {
      return DateValidator.class;
    } else if(validatorType.equals("cronExpressionValidator")) {
      return CronExpressionValidator.class;
    }else if(validatorType.equals("repeatCountValidator")) {
      return RepeatCountValidator.class;
    }else if(validatorType.equals("repeatIntervalValidator")) {
      return RepeatIntervalValidator.class;
    }else if (validatorType.equals("length")){
      return StringLengthValidator.class;
    }else if (validatorType.equals("category")){
      return CategoryValidator.class;
    }else {
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return cl.loadClass(validatorType);
    }
  }
  @SuppressWarnings("unchecked")
  public static void addValidators(UIFormInputBase uiInput, String validators) throws Exception {
    String[] validatorList = null;
    if (validators.indexOf(',') > -1) validatorList = validators.split(",");
    else validatorList = new String[] {validators};
    for (String validator : validatorList) {
      Object[] params;
      String s_param=null;
      int p_begin, p_end;
      p_begin = validator.indexOf(VALIDATOR_PARAM_BEGIN);
      p_end   = validator.indexOf(VALIDATOR_PARAM_END);
      if (p_begin>0 && p_end > p_begin) {
        String v_name;
        s_param = validator.substring(p_begin+1, p_end);
        params = s_param.split(VALIDATOR_PARAM_SEPERATOR);
        params = parserValidatorParam(params, params.length-1, params[params.length-1].toString());
        v_name = validator.substring(0, p_begin);
        uiInput.addValidator(getValidator(v_name.trim()), params) ;
      }else {
        uiInput.addValidator(getValidator(validator.trim())) ;
      }
    }
  }
  /**
   *
   * @param params
   * @param length
   * @param type
   * @return the conversion of the input parameters with the new type.
   * @throws Exception
   */
  public static Object[] parserValidatorParam(Object[] params, int length, String type) throws Exception {
    int i;
    Object[] newParams;
    if (length<1) return params;
    newParams = new Object[length];
    if (type.equalsIgnoreCase(TYPE_INTEGER)) {
      for (i=0; i<length; i++ ) newParams[i] = Integer.parseInt(params[i].toString());
    }else if (type.equalsIgnoreCase(TYPE_FLOAT)){
      for (i=0; i<length; i++ ) newParams[i] = Float.parseFloat(params[i].toString());
    }else if (type.equalsIgnoreCase(TYPE_DOUBLE)) {
      for (i=0; i<length; i++ ) newParams[i] = Double.parseDouble(params[i].toString());
    }else return params;//Do not convert, let those parameters are the Objec type
    return newParams;
  }
}
