package com.thaiopensource.datatype.xsd;

import com.thaiopensource.datatype.xsd.regex.RegexSyntaxException;
import com.thaiopensource.util.Localizer;
import org.relaxng.datatype.Datatype;
import org.relaxng.datatype.DatatypeBuilder;
import org.relaxng.datatype.DatatypeException;
import org.relaxng.datatype.ValidationContext;

class DatatypeBuilderImpl implements DatatypeBuilder {
  static final Localizer localizer = new Localizer(DatatypeBuilderImpl.class);

  private DatatypeBase base;
  private final DatatypeLibraryImpl library;

  DatatypeBuilderImpl(DatatypeLibraryImpl library, DatatypeBase base) throws DatatypeException {
    this.library = library;
    this.base = base;
  }

  public void addParameter(String name,
			   String value,
			   ValidationContext context) throws DatatypeException {
    if (name.equals("pattern"))
      addPatternParam(value);
    else if (name.equals("minInclusive"))
      addMinInclusiveParam(value, context);
    else if (name.equals("maxInclusive"))
      addMaxInclusiveParam(value, context);
    else if (name.equals("minExclusive"))
      addMinExclusiveParam(value, context);
    else if (name.equals("maxExclusive"))
      addMaxExclusiveParam(value, context);
    else if (name.equals("length"))
      addLengthParam(value);
    else if (name.equals("minLength"))
      addMinLengthParam(value);
    else if (name.equals("maxLength"))
      addMaxLengthParam(value);
    else if (name.equals("fractionDigits"))
      addScaleParam(value);
    else if (name.equals("totalDigits"))
      addPrecisionParam(value);
    else if (name.equals("enumeration"))
      error("enumeration_param");
    else if (name.equals("whiteSpace"))
      error("whiteSpace_param");
    else
      error("unrecognized_param", name);
  }

  private void addPatternParam(String value) throws DatatypeException {
    try {
      base = new PatternRestrictDatatype(base,
					 library.getRegexEngine().compile(value),
                                         value);
    }
    catch (RegexSyntaxException e) {
      int pos = e.getPosition();
      if (pos == RegexSyntaxException.UNKNOWN_POSITION)
        pos = DatatypeException.UNKNOWN;
      error("invalid_regex", e.getMessage(), pos);
    }
  }

  private void addMinInclusiveParam(String value, ValidationContext context)
    throws DatatypeException {
    base = new MinInclusiveRestrictDatatype(base,
					    getLimit(value, context),
                                            value);
  }

  private void addMaxInclusiveParam(String value, ValidationContext context)
    throws DatatypeException {
    base = new MaxInclusiveRestrictDatatype(base,
					    getLimit(value, context),
                                            value);
  }

  private void addMinExclusiveParam(String value, ValidationContext context)
    throws DatatypeException {
    base = new MinExclusiveRestrictDatatype(base,
					    getLimit(value, context),
                                            value);
  }

  private void addMaxExclusiveParam(String value, ValidationContext context)
    throws DatatypeException {
    base = new MaxExclusiveRestrictDatatype(base,
					    getLimit(value, context),
                                            value);
  }

  private Object getLimit(String str, ValidationContext context)
    throws DatatypeException {
    if (base.getOrderRelation() == null)
      error("not_ordered");
    str = base.normalizeWhiteSpace(str);
    try {
      base.checkLexicallyAllows(str);
      return base.getValue(str, context);
    }
    catch (DatatypeException e) {
      throw new DatatypeException(localizer.message("invalid_limit", str, e.getMessage()));
    }
  }

  private void addLengthParam(String value) throws DatatypeException {
    base = new LengthRestrictDatatype(base, getLength(value));
  }

  private void addMinLengthParam(String value) throws DatatypeException {
    base = new MinLengthRestrictDatatype(base, getLength(value));
  }

  private void addMaxLengthParam(String value) throws DatatypeException {
    base = new MaxLengthRestrictDatatype(base, getLength(value));
  }

  private int getLength(String str) throws DatatypeException {
    if (base.getMeasure() == null)
      error("no_length");
    int len = convertNonNegativeInteger(str);
    if (len < 0)
      error("length_not_non_negative_integer");
    return len;
  }
    
  private void addScaleParam(String str) throws DatatypeException {
    if (!(base.getPrimitive() instanceof DecimalDatatype))
      error("scale_not_derived_from_decimal");
    int scale = convertNonNegativeInteger(str);
    if (scale < 0)
      error("scale_not_non_negative_integer");
    base = new ScaleRestrictDatatype(base, scale);
  }

  private void addPrecisionParam(String str) throws DatatypeException {
    if (!(base.getPrimitive() instanceof DecimalDatatype))
      error("precision_not_derived_from_decimal");
    int scale = convertNonNegativeInteger(str);
    if (scale <= 0)
      error("precision_not_positive_integer");
    base = new PrecisionRestrictDatatype(base, scale);
  }

  public Datatype createDatatype() {
    return base;
  }

  private static void error(String key) throws DatatypeException {
    throw new DatatypeException(localizer.message(key));
  }

  private static void error(String key, String arg) throws DatatypeException {
    throw new DatatypeException(localizer.message(key, arg));
  }

  private static void error(String key, String arg, int pos) throws DatatypeException {
    throw new DatatypeException(pos, localizer.message(key, arg));
  }

  // Return -1 for anything that is not a nonNegativeInteger
  // Return Integer.MAX_VALUE for values that are too big

  private int convertNonNegativeInteger(String str) {
    str = str.trim();
    DecimalDatatype decimal = new DecimalDatatype();
    if (!decimal.lexicallyAllows(str))
      return -1;
    // Canonicalize the value
    str = decimal.getValue(str, null).toString();
    // Reject negative and fractional numbers
    if (str.charAt(0) == '-' || str.indexOf('.') >= 0)
      return -1;
    try {
      return Integer.parseInt(str);
    }
    catch (NumberFormatException e) {
      // Map out of range integers to MAX_VALUE
      return Integer.MAX_VALUE;
    }
  }
}
