/**********************************************************************************
 * $URL: https://source.sakaiproject.org/svn/metaobj/tags/sakai_2-3-1/metaobj-impl/api-impl/src/java/org/sakaiproject/metaobj/utils/xml/impl/BaseElementType.java $
 * $Id: BaseElementType.java 14225 2006-09-05 17:39:44Z chmaurer@iupui.edu $
 ***********************************************************************************
 *
 * Copyright (c) 2004, 2005, 2006 The Sakai Foundation.
 * 
 * Licensed under the Educational Community License, Version 1.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 * 
 *      http://www.opensource.org/licenses/ecl1.php
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 *
 **********************************************************************************/

package org.sakaiproject.metaobj.utils.xml.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.Namespace;
import org.sakaiproject.metaobj.utils.xml.ElementType;
import org.sakaiproject.metaobj.utils.xml.NormalizationException;
import org.sakaiproject.metaobj.utils.xml.SchemaNode;
import org.sakaiproject.metaobj.utils.xml.ValidatedNode;
import org.sakaiproject.metaobj.utils.xml.ValidationError;
import org.sakaiproject.metaobj.utils.xml.ValueRange;

/**
 * Created by IntelliJ IDEA.
 * User: John Ellis
 * Date: Apr 15, 2004
 * Time: 5:07:44 PM
 * To change this template use File | Settings | File Templates.
 */
public class BaseElementType implements ElementType {

   protected static final int WHITE_SPACE_TYPE_NONE = 0;
   protected static final int WHITE_SPACE_TYPE_PRESERVE = 1;
   // replace with spaces
   protected static final int WHITE_SPACE_TYPE_REPLACE = 2;
   // remove all whitespace and replace with spaces
   protected static final int WHITE_SPACE_TYPE_COLLAPSE = 3;

   protected int length = -1;
   protected int maxLength = -1;
   protected int minLength = -1;
   protected Pattern pattern = null;
   protected int whiteSpaceType = WHITE_SPACE_TYPE_NONE;
   protected String defaultValue = "";
   protected String fixedValue = "";
   protected Element schemaElement;
   protected SchemaNode parentNode = null;
   private List enumeration = null;
   private String baseType;

   public BaseElementType(String typeName, Element schemaElement, SchemaNode parentNode,
                          Namespace xsdNamespace) {
      setBaseType(typeName);
      this.schemaElement = schemaElement;
      this.parentNode = parentNode;
      init(xsdNamespace);
   }

   protected void init(Namespace xsdNamespace) {
      Element simpleType = schemaElement.getChild("simpleType", xsdNamespace);
      if (simpleType != null) {
         Element restrictions = simpleType.getChild("restriction", xsdNamespace);
         if (restrictions != null) {
            // process restrictions
            length = processIntRestriction(restrictions, "length", xsdNamespace, length);
            maxLength = processIntRestriction(restrictions, "maxLength", xsdNamespace, maxLength);
            minLength = processIntRestriction(restrictions, "minLength", xsdNamespace, minLength);

            String patternValue = processStringRestriction(restrictions, "pattern", xsdNamespace);

            if (patternValue != null) {
               pattern = Pattern.compile(patternValue);
            }

            String whiteSpaceValue = processStringRestriction(restrictions, "whiteSpace", xsdNamespace);
            if (whiteSpaceValue != null) {
               if (whiteSpaceValue.equals("preserve")) {
                  whiteSpaceType = WHITE_SPACE_TYPE_PRESERVE;
               }
               if (whiteSpaceValue.equals("replace")) {
                  whiteSpaceType = WHITE_SPACE_TYPE_REPLACE;
               }
               if (whiteSpaceValue.equals("collapse")) {
                  whiteSpaceType = WHITE_SPACE_TYPE_COLLAPSE;
               }
            }
         }
      }

      if (schemaElement.getAttribute("default") != null) {
         defaultValue = schemaElement.getAttributeValue("default");
      }
      if (schemaElement.getAttribute("fixed") != null) {
         fixedValue = schemaElement.getAttributeValue("fixed");
         defaultValue = fixedValue;
      }
   }

   protected void processEnumerations(Element restrictions, Namespace xsdNamespace) {
      List enums = restrictions.getChildren("enumeration", xsdNamespace);
      List temp = null;
      enumeration = null;

      if (enums.size() > 0) {
         temp = new ArrayList();
      }

      for (Iterator i = enums.iterator(); i.hasNext();) {
         Element enumer = (Element) i.next();
         String value = enumer.getAttributeValue("value");
         temp.add(getActualNormalizedValue(value));
      }

      enumeration = temp;
   }

   protected String processStringRestriction(Element restrictions, String s, Namespace xsdNamespace) {
      Element currentRestriction = restrictions.getChild(s, xsdNamespace);

      if (currentRestriction == null) {
         return null;
      }

      return currentRestriction.getAttributeValue("value");
   }

   protected int processIntRestriction(Element restrictions, String s, Namespace xsdNamespace, int defaultValue) {
      String value = processStringRestriction(restrictions, s, xsdNamespace);

      if (value == null) {
         return defaultValue;
      }

      return Integer.parseInt(value);
   }


   /**
    * Validates the passed in node and all children.
    * Will also normalize any values.
    *
    * @param node a jdom element to validate
    * @return the validated Element wrapped
    *         in a ValidatedNode class
    */
   public ValidatedNode validateAndNormalize(Element node) {
      ValidatedNodeImpl validatedNode =
            new ValidatedNodeImpl(parentNode, node);

      String value = node.getText();

      try {
         value = getSchemaNormalizedValue(value);
         node.setText(value);
         validatedNode.setNormalizedValue(getActualNormalizedValue(value));

         if (value == null || value.length() == 0) {
            return null;
         }
      }
      catch (NormalizationException exp) {
         validatedNode.getErrors().add(new ValidationError(validatedNode, exp.getErrorCode(), exp.getErrorInfo()));
      }

      return validatedNode;
   }

   public String getSchemaNormalizedValue(Object value) throws NormalizationException {

      if (value == null) {
         return null;
      }

      return getSchemaNormalizedValue(value.toString());
   }

   public String getSchemaNormalizedValue(String value) throws NormalizationException {
      if (value == null) {
         return defaultValue;
      }

      if (fixedValue != null && fixedValue.length() > 0) {
         return fixedValue;
      }

      String startingValue = handleWhiteSpace(value.toString());

      int valueLength = startingValue.length();

      if (length != -1 && valueLength != length) {
         throw new NormalizationException("Invalid string length",
               NormalizationException.INVALID_LENGTH_ERROR_CODE, new Object[]{startingValue, new Integer(length)});
      }

      if (maxLength != -1 && valueLength > maxLength) {
         throw new NormalizationException("Invalid string length",
               NormalizationException.INVALID_LENGTH_TOO_LONG_ERROR_CODE,
               new Object[]{startingValue, new Integer(maxLength)});
      }

      if (minLength != -1 && valueLength < minLength && minLength == 1) {
         throw new NormalizationException("Required field",
               NormalizationException.REQIRED_FIELD_ERROR_CODE, new Object[0]);
      }

      if (minLength != -1 && valueLength < minLength) {
         throw new NormalizationException("Invalid string length",
               NormalizationException.INVALID_LENGTH_TOO_SHORT_ERROR_CODE,
               new Object[]{startingValue, new Integer(minLength)});
      }

      if (pattern != null && !pattern.matcher(startingValue).matches()) {
         throw new NormalizationException("Invalid string pattern",
               NormalizationException.INVALID_PATTERN_MATCH_ERROR_CODE,
               new Object[]{startingValue, pattern.pattern()});
      }

      return startingValue;
   }

   private String handleWhiteSpace(String s) {
      if (whiteSpaceType == WHITE_SPACE_TYPE_NONE ||
            whiteSpaceType == WHITE_SPACE_TYPE_PRESERVE) {
         return s;
      }

      if (whiteSpaceType == WHITE_SPACE_TYPE_REPLACE) {
         s = s.replaceAll("\\s", " ");
      }
      else if (whiteSpaceType == WHITE_SPACE_TYPE_COLLAPSE) {
         s = s.replaceAll("\\s+", " ");
      }

      return s;
   }

   public Class getObjectType() {
      return String.class;
   }

   public Object getActualNormalizedValue(String value) {
      Object returned = getSchemaNormalizedValue(value);

      if (enumeration != null) {
         if (!enumeration.contains(returned)) {
            throw new NormalizationException("Not enumerated",
                  NormalizationException.NOT_IN_ENUMERATION_ERROR_CODE,
                  new Object[]{returned});
         }
      }

      return returned;
   }


   public int getLength() {
      return length;
   }

   public int getMaxLength() {
      return maxLength;
   }

   public int getMinLength() {
      return minLength;
   }

   public Pattern getPattern() {
      return pattern;
   }

   public ValueRange getRange() {
      return null;
   }

   public int getWhiteSpaceType() {
      return whiteSpaceType;
   }

   public String getDefaultValue() {
      return defaultValue;
   }

   public String getFixedValue() {
      return fixedValue;
   }

   public List getEnumeration() {
      return enumeration;
   }

   public BaseElementType postInit(Namespace xsdNamespace) {
      Element simpleType = schemaElement.getChild("simpleType", xsdNamespace);
      if (simpleType != null) {
         Element restrictions = simpleType.getChild("restriction", xsdNamespace);

         if (restrictions != null) {
            processEnumerations(restrictions, xsdNamespace);
         }
      }

      return this;
   }

   public ValidatedNode validateAndNormalize(Attribute node) {
      ValidatedNodeImpl validatedNode =
            new ValidatedNodeImpl(parentNode, null);

      String value = node.getValue();

      try {
         value = getSchemaNormalizedValue(value);
         node.setValue(value);
         validatedNode.setNormalizedValue(getActualNormalizedValue(value));

         if (value == null || value.length() == 0) {
            return null;
         }
      }
      catch (NormalizationException exp) {
         validatedNode.getErrors().add(new ValidationError(validatedNode, exp.getErrorCode(), exp.getErrorInfo()));
      }

      return validatedNode;
   }

   public String getBaseType() {
      return baseType;
   }

   public void setBaseType(String baseType) {
      this.baseType = baseType;
   }

}
