// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
//
// 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 com.google.collide.client.code.autocomplete.css;

import com.google.collide.client.code.autocomplete.AutocompleteProposal;
import com.google.collide.json.client.Jso;
import com.google.collide.json.client.JsoArray;
import com.google.common.annotations.VisibleForTesting;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.regexp.shared.RegExp;

import elemental.js.util.JsMapFromStringToBoolean;

/**
 * Based on the complete property list for CSS2, this partial parser parses
 * already existing values and proposes autocompletions for the slot where the
 * cursor currently is.
 *
 */
public class CssPartialParser {

  /**
   * Singleton instance.
   */
  private static CssPartialParser instance;

  // TODO: What about "-.5"?
  private static final String NUMBER_PATTERN = "(-|\\+)?\\d+(\\.\\d+)?";
  private static final String PERCENTAGE_PATTERN = "(\\d|[1-9]\\d|100)%";
  private static final String BYTE_PATTERN = "(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])";
  private static final String LENGTH_PATTERN = NUMBER_PATTERN + "(em|ex|in|cm|mm|pt|pc|px)";

  private static final RegExp REGEXP_INTEGER = compileRegExp("(-|\\+)?\\d+");
  private static final RegExp REGEXP_PERCENTAGE = compileRegExp(PERCENTAGE_PATTERN);
  private static final RegExp REGEXP_3_HEX = compileRegExp("#[0-9a-fA-F]{3}");
  private static final RegExp REGEXP_6_HEX = compileRegExp("#[0-9a-fA-F]{6}");
  private static final RegExp REGEXP_RGB_BYTES = compileRegExp(
      "rgb\\(" + BYTE_PATTERN + "\\,\\s*" + BYTE_PATTERN + "\\,\\s*" + BYTE_PATTERN + "\\)");
  private static final RegExp REGEXP_RGB_PERCENTS = compileRegExp("rgb\\(" + PERCENTAGE_PATTERN
      + "\\,\\s*" + PERCENTAGE_PATTERN + "\\,\\s*" + PERCENTAGE_PATTERN + "\\)");
  private static final RegExp REGEXP_ANGLE = compileRegExp(NUMBER_PATTERN + "(deg|grad|rad)");
  private static final RegExp REGEXP_NUMBER = compileRegExp(NUMBER_PATTERN);
  private static final RegExp REGEXP_FREQUENCY = compileRegExp(NUMBER_PATTERN + "k?Hz");
  private static final RegExp REGEXP_LENGTH = compileRegExp(LENGTH_PATTERN);
  private static final RegExp REGEXP_RECT = compileRegExp("rect\\(\\s*("
      + LENGTH_PATTERN + "|auto)\\s*\\,\\s*(" + LENGTH_PATTERN + "|auto)\\s*\\,\\s*("
      + LENGTH_PATTERN + "|auto)\\s*\\,\\s*(" + LENGTH_PATTERN + "|auto)\\s*\\)");

  private static final String ANGLE = "<angle>";
  private static final String NUMBER = "<number>";
  private static final String INTEGER = "<integer>";
  private static final String URI = "<uri>";
  private static final String PERCENTAGE = "<percentage>";
  private static final String STRING = "<string>";
  private static final String COUNTER = "<counter>";
  private static final String IDENTIFIER = "<identifier>";
  private static final String FREQUENCY = "<frequency>";
  private static final String COLOR = "<color>";
  private static final String LENGTH = "<length>";
  private static final String SHAPE = "<shape>";

  private final JavaScriptObject allProperties;
  private final JavaScriptObject specialValueProposals;
  private final JsMapFromStringToBoolean repeatingProperties = JsMapFromStringToBoolean.create();

  public static CssPartialParser getInstance() {
    if (instance == null) {
      instance = new CssPartialParser();
    }
    return instance;
  }

  /**
   * Creates {@link RegExp} instance by given pattern that matches the
   * whole string.
   */
  private static RegExp compileRegExp(String pattern) {
    return RegExp.compile("^" + pattern + "$");
  }

  private CssPartialParser() {
    allProperties = setupAllProperties();
    specialValueProposals = setupSpecialValueProposals();
    setupRepeatingProperties(repeatingProperties);
  }

  private boolean checkIfAngle(String maybeSpecialValue) {
    return REGEXP_ANGLE.test(maybeSpecialValue);
  }

  private boolean checkIfNumber(String maybeSpecialValue) {
    return REGEXP_NUMBER.test(maybeSpecialValue);
  }

  private boolean checkIfInteger(String maybeSpecialValue) {
    return REGEXP_INTEGER.test(maybeSpecialValue);
  }

  private boolean checkIfUri(String maybeSpecialValue) {
    // TODO: This is an oversimplification.
    return (maybeSpecialValue.startsWith("http://") || maybeSpecialValue.startsWith("https://"));
  }

  private boolean checkIfPercentage(String maybeSpecialValue) {
    return REGEXP_PERCENTAGE.test(maybeSpecialValue);
  }

  private boolean checkIfString(String maybeSpecialValue) {
    return (((maybeSpecialValue.charAt(0) == '"')
        && (maybeSpecialValue.charAt(maybeSpecialValue.length() - 1) == '"'))
        || ((maybeSpecialValue.charAt(0) == '\'')
            && (maybeSpecialValue.charAt(maybeSpecialValue.length() - 1) == '\'')));
  }

  private boolean checkIfCounter(String maybeSpecialValue) {
    // TODO: This is a simplification.
    return maybeSpecialValue.startsWith("counter(");
  }

  private boolean checkIfIdentifier(String maybeSpecialValue) {
    // TODO: Implement this.
    return false;
  }

  private boolean checkIfFrequency(String maybeSpecialValue) {
    return REGEXP_FREQUENCY.test(maybeSpecialValue);
  }

  private boolean checkIfColor(String maybeSpecialValue) {
    return maybeSpecialValue.equals("aqua") || maybeSpecialValue.equals("black")
        || maybeSpecialValue.equals("blue") || maybeSpecialValue.equals("fuchsia")
        || maybeSpecialValue.equals("gray") || maybeSpecialValue.equals("green")
        || maybeSpecialValue.equals("lime") || maybeSpecialValue.equals("maroon")
        || maybeSpecialValue.equals("navy") || maybeSpecialValue.equals("olive")
        || maybeSpecialValue.equals("orange") || maybeSpecialValue.equals("purple")
        || maybeSpecialValue.equals("red") || maybeSpecialValue.equals("silver")
        || maybeSpecialValue.equals("teal") || maybeSpecialValue.equals("white")
        || maybeSpecialValue.equals("yellow")
        || REGEXP_3_HEX.test(maybeSpecialValue)
        || REGEXP_6_HEX.test(maybeSpecialValue)
        || REGEXP_RGB_BYTES.test(maybeSpecialValue)
        || REGEXP_RGB_PERCENTS.test(maybeSpecialValue);
  }

  private boolean checkIfLength(String maybeSpecialValue) {
    return REGEXP_LENGTH.test(maybeSpecialValue);
  }

  private boolean checkIfShape(String maybeSpecialValue) {
    // Note: the syntax for shape is rect(<top>, <right>, <bottom>, <left>)
    return REGEXP_RECT.test(maybeSpecialValue);
  }

  /**
   * Checks whether the passed-in string matches the format of the special value
   * type in question. If it does, it returns a list of proposals that should be
   * shown to the user for this query.
   *
   * @param maybeSpecialValue
   * @param specialValueType a special value type, e.g., <integer>. This method
   *        can be called with a specialValueType value that is not one of those
   *        special values, in which case the method returns an empty array.
   * @return an array of strings corresponding to the proposals that should be
   *         shown for the special value type
   */
  @VisibleForTesting
  public JsArrayString checkIfSpecialValueAndGetSpecialValueProposals(
      String maybeSpecialValue, String specialValueType) {

    JsArrayString specialValues = getSpecialValues(maybeSpecialValue);
    for (int i = 0; i < specialValues.length(); i++) {
      if (specialValues.get(i).equals(specialValueType)) {
        return specialValueProposals.<Jso>cast().getJsObjectField(specialValueType).cast();
      }
    }
    return JavaScriptObject.createArray().cast();
  }

  public JsoArray<AutocompleteProposal> getAutocompletions(
      String property, JsArrayString valuesBefore, String incomplete, JsArrayString valuesAfter) {
    incomplete = incomplete.toLowerCase();
    boolean isRepeatingProperty = repeatingProperties.hasKey(property);
    JsArrayString valuesAndSpecialValuesAfter = getValuesAndSpecialValues(valuesAfter);
    JsArrayString valuesAndSpecialValuesBefore = getValuesAndSpecialValues(valuesBefore);

    JsoArray<AutocompleteProposal> proposals = JsoArray.create();
    JsArray<JavaScriptObject> valuesForAllSlots = getPropertyValues(property);
    if (valuesForAllSlots == null) {
      return proposals;
    }
    int numSlots = valuesForAllSlots.length();

    if (numSlots == 0) {
      return proposals;
    }
    int slot = valuesBefore.length(); // slots use 0-based counting

    if (slot >= numSlots) {
      // before giving up, see if the last entry is +, in which case we just
      // adjust the slot number
      JavaScriptObject lastSlotValues = valuesForAllSlots.get(numSlots - 1);
      JsArrayString keySet = getKeySet(lastSlotValues);
      if ((keySet.length() == 1) && (keySet.get(0).equals("+"))) {
        slot = numSlots - 1;
      } else {
        return proposals;
      }
    }
    JavaScriptObject valuesForSlotInQuestion = valuesForAllSlots.get(slot);

    JsArrayString keySet = getKeySet(valuesForSlotInQuestion);
    if ((keySet.length() == 1) && (keySet.get(0).equals("+"))) {
      valuesForSlotInQuestion = valuesForAllSlots.get(slot - 1);
      keySet = getKeySet(valuesForSlotInQuestion);
    }

    if (valuesForSlotInQuestion != null) {
      if (keySet.length() == 0) {
        return proposals;
      }

      for (int keyCt = 0; keyCt < keySet.length(); keyCt++) {
        String currentValue = keySet.get(keyCt);
        Boolean shouldBeIncluded = false;
        // TODO: Avoid using untyped native collections.
        JavaScriptObject triggers =
            valuesForSlotInQuestion.<Jso>cast().getJsObjectField(currentValue).cast();
        JsArrayString keyTriggerSet = getKeySet(triggers);
        if (keyTriggerSet.length() == 0) {
          if (currentValue.charAt(0) == '<') {
            JsArrayString valueProposals =
                specialValueProposals.<Jso>cast().getJsObjectField(currentValue).cast();
            if (valueProposals != null && valueProposals.length() != 0) {
              shouldBeIncluded = false;
              for (int i = 0; i < valueProposals.length(); i++) {
                if (valueProposals.get(i).startsWith(incomplete)
                    && (isRepeatingProperty || !inExistingValues(
                        currentValue, valuesAndSpecialValuesBefore, valuesAndSpecialValuesAfter))) {
                  proposals.add(new AutocompleteProposal(valueProposals.get(i)));
                }
              }
            }
          } else {
            shouldBeIncluded = true;
          }
        } else {
          for (int keyTriggerCt = 0; keyTriggerCt < keyTriggerSet.length(); keyTriggerCt++) {
            String triggerValue = keyTriggerSet.get(keyTriggerCt);
            int triggerSlot = triggers.<Jso>cast().getIntField(triggerValue);
            if (triggerValue.charAt(0) == '<') {
              JsArrayString specialValueProposalsAfterCheck =
                  checkIfSpecialValueAndGetSpecialValueProposals(
                      valuesBefore.get(triggerSlot), triggerValue);
              if (specialValueProposalsAfterCheck.length() != 0) {
                shouldBeIncluded = false;
                for (int i = 0; i < specialValueProposalsAfterCheck.length(); i++) {
                  if (specialValueProposalsAfterCheck.get(i).startsWith(incomplete)
                      && (isRepeatingProperty
                          || !inExistingValues(triggerValue, valuesAndSpecialValuesBefore,
                              valuesAndSpecialValuesAfter))) {
                    proposals.add(new AutocompleteProposal(specialValueProposalsAfterCheck.get(i)));
                  }
                }
              }
            } else if (valuesBefore.get(triggerSlot).compareTo(triggerValue) == 0) {
              shouldBeIncluded = true;
            }
          }
        }
        if (shouldBeIncluded) {
          if (currentValue.startsWith(incomplete) && (isRepeatingProperty || !inExistingValues(
              currentValue, valuesAndSpecialValuesBefore, valuesAndSpecialValuesAfter))) {
            proposals.add(new AutocompleteProposal(currentValue));
          }
        }
      }
    }
    return proposals;
  }

  private boolean inExistingValues(String currentValue, JsArrayString valuesAndSpecialValuesAfter,
      JsArrayString valuesAndSpecialValuesBefore) {
    for (int i = 0; i < valuesAndSpecialValuesAfter.length(); i++) {
      if (valuesAndSpecialValuesAfter.get(i).equals(currentValue)) {
        return true;
      }
    }
    for (int i = 0; i < valuesAndSpecialValuesBefore.length(); i++) {
      if (valuesAndSpecialValuesBefore.get(i).equals(currentValue)) {
        return true;
      }
    }
    return false;
  }

  private JsArrayString getValuesAndSpecialValues(JsArrayString existingValues) {
    JsArrayString valuesAndSpecialValuesAfter = JavaScriptObject.createArray().cast();
    for (int i = 0; i < existingValues.length(); i++) {
      String value = existingValues.get(i);
      JsArrayString specialValues = getSpecialValues(value);
      if (specialValues.length() == 0) {
        valuesAndSpecialValuesAfter.push(value);
      }
      for (int j = 0; j < specialValues.length(); j++) {
        valuesAndSpecialValuesAfter.push(specialValues.get(j));
      }
    }
    return valuesAndSpecialValuesAfter;
  }

  private JsArrayString getSpecialValues(String value) {
    JsArrayString specialValues = JavaScriptObject.createArray().cast();
    if (value.isEmpty()) {
      return specialValues;
    }
    if (checkIfAngle(value)) {
      specialValues.push(ANGLE);
    }
    if (checkIfInteger(value)) {
      specialValues.push(INTEGER);
    }
    if (checkIfNumber(value)) {
      specialValues.push(NUMBER);
    }
    if (checkIfUri(value)) {
      specialValues.push(URI);
    }
    if (checkIfPercentage(value)) {
      specialValues.push(PERCENTAGE);
    }
    if (checkIfString(value)) {
      specialValues.push(STRING);
    }
    if (checkIfCounter(value)) {
      specialValues.push(COUNTER);
    }
    if (checkIfIdentifier(value)) {
      specialValues.push(IDENTIFIER);
    }
    if (checkIfFrequency(value)) {
      specialValues.push(FREQUENCY);
    }
    if (checkIfColor(value)) {
      specialValues.push(COLOR);
    }
    if (checkIfLength(value)) {
      specialValues.push(LENGTH);
    }
    if (checkIfShape(value)) {
      specialValues.push(SHAPE);
    }
    return specialValues;
  }

  @VisibleForTesting
  public JsArray<JavaScriptObject> getPropertyValues(String property) {
    return allProperties.<Jso>cast().getJsObjectField(property).cast();
  }

  private native JsArrayString getKeySet(JavaScriptObject jso) /*-{
    var accumulator = [];
    for ( var propertyName in jso) {
      accumulator.push(propertyName);
    }
    return accumulator;
  }-*/;

  private native JavaScriptObject setupAllProperties() /*-{
    return {
        'azimuth' : [
            {
                'left-side' : {},
                'far-left' : {},
                'center-left' : {},
                'center' : {},
                'center-right' : {},
                'right' : {},
                'far-right' : {},
                'right-side' : {},
                '<angle>' : {},
                'leftwards' : {},
                'rightwards' : {},
                'inherit' : {}
            }, {
              'behind' : {
                  'left-side' : 0,
                  'far-left' : 0,
                  'center-left' : 0,
                  'center' : 0,
                  'center-right' : 0,
                  'right' : 0,
                  'far-right' : 0,
                  'right-side' : 0,
                  '<angle>' : 0
              }
            }
        ],
        'background' : [
            {
                '<color>' : {},
                'transparent' : {},
                'inherit' : {}
            }, {
                '<uri>' : {},
                'none' : {},
                'inherit' : {}
            }, {
                'repeat' : {},
                'repeat-x' : {},
                'repeat-y' : {},
                'no-repeat' : {},
                'inherit' : {}
            }, {
                'scroll' : {},
                'fixed' : {},
                'inherit' : {}
            }, {
                '<percentage>' : {},
                '<length>' : {},
                'left' : {},
                'center' : {},
                'right' : {},
                'inherit' : {}
            }, {
                '<percentage>' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                },
                '<length>' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                },
                'top' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                },
                'center' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                },
                'bottom' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                }
            }
        ],
        'background-attachment' : [
          {
              'scroll' : {},
              'fixed' : {},
              'inherit' : {}
          }
        ],
        'background-color' : [
          {
              '<color>' : {},
              'transparent' : {},
              'inherit' : {}
          }
        ],
        'background-image' : [
          {
              '<uri>' : {},
              'none' : {},
              'inherit' : {}
          }
        ],
        'background-position' : [
            {
                '<percentage>' : {},
                '<length>' : {},
                'left' : {},
                'center' : {},
                'right' : {},
                'inherit' : {}
            }, {
                '<percentage>' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                },
                '<length>' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                },
                'top' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                },
                'center' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                },
                'bottom' : {
                    '<percentage>' : 0,
                    '<length>' : 0,
                    'left' : 0,
                    'center' : 0,
                    'right' : 0
                }
            }
        ],
        'background-repeat' : [
          {
              'repeat' : {},
              'repeat-x' : {},
              'repeat-y' : {},
              'no-repeat' : {},
              'inherit' : {}
          }
        ],
        'border-collapse' : [
          {
              'collapse' : {},
              'separate' : {},
              'inherit' : {}
          }
        ],
        'border-color' : [
            {
                '<color>' : {},
                'transparent' : {},
                'inherit' : {}
            }, {
                '<color>' : {
                    '<color>' : 0,
                    'transparent' : 0
                },
                'transparent' : {
                    '<color>' : 0,
                    'transparent' : 0
                }
            }, {
                '<color>' : {
                    '<color>' : 1,
                    'transparent' : 1
                },
                'transparent' : {
                    '<color>' : 1,
                    'transparent' : 1
                }
            }, {
                '<color>' : {
                    '<color>' : 2,
                    'transparent' : 2
                },
                'transparent' : {
                    '<color>' : 2,
                    'transparent' : 2
                }
            }
        ],
        'border-spacing' : [
            {
                '<length>' : {},
                'inherit' : {}
            }, {
              '<length>' : {
                '<length>' : 0
              }
            }
        ],
        'border-style' : [
            {
                'none' : {},
                'hidden' : {},
                'dotted' : {},
                'dashed' : {},
                'solid' : {},
                'double' : {},
                'groove' : {},
                'ridge' : {},
                'inset' : {},
                'outset' : {},
                'inherit' : {}
            }, {
                'none' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                },
                'hidden' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                },
                'dotted' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                },
                'dashed' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                },
                'solid' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                },
                'double' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                },
                'groove' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                },
                'ridge' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                },
                'inset' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                },
                'outset' : {
                    'none' : 0,
                    'hidden' : 0,
                    'dotted' : 0,
                    'dashed' : 0,
                    'solid' : 0,
                    'double' : 0,
                    'groove' : 0,
                    'ridge' : 0,
                    'inset' : 0,
                    'outset' : 0
                }
            }, {
                'none' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                },
                'hidden' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                },
                'dotted' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                },
                'dashed' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                },
                'solid' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                },
                'double' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                },
                'groove' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                },
                'ridge' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                },
                'inset' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                },
                'outset' : {
                    'none' : 1,
                    'hidden' : 1,
                    'dotted' : 1,
                    'dashed' : 1,
                    'solid' : 1,
                    'double' : 1,
                    'groove' : 1,
                    'ridge' : 1,
                    'inset' : 1,
                    'outset' : 1
                }
            }, {
                'none' : {
                    'none' : 2,
                    'hidden' : 2,
                    'dotted' : 2,
                    'dashed' : 2,
                    'solid' : 2,
                    'double' : 2,
                    'groove' : 2,
                    'ridge' : 2,
                    'inset' : 2,
                    'outset' : 2
                },
                'hidden' : {
                    'none' : 2,
                    'hidden' : 2,
                    'dotted' : 2,
                    'dashed' : 2,
                    'solid' : 2,
                    'double' : 2,
                    'groove' : 2,
                    'ridge' : 2,
                    'inset' : 2,
                    'outset' : 2
                },
                'dotted' : {
                    'none' : 2,
                    'hidden' : 2,
                    'dotted' : 2,
                    'dashed' : 2,
                    'solid' : 2,
                    'double' : 2,
                    'groove' : 2,
                    'ridge' : 2,
                    'inset' : 2,
                    'outset' : 2
                },
                'dashed' : {
                    'none' : 2,
                    'hidden' : 2,
                    'dotted' : 2,
                    'dashed' : 2,
                    'solid' : 2,
                    'double' : 2,
                    'groove' : 2,
                    'ridge' : 2,
                    'inset' : 2,
                    'outset' : 2
                },
                'solid' : {
                    'none' : 2,
                    'hidden' : 2,
                    'dotted' : 2,
                    'dashed' : 2,
                    'solid' : 2,
                    'double' : 2,
                    'groove' : 2,
                    'ridge' : 2,
                    'inset' : 2,
                    'outset' : 2
                },
                'double' : {
                    'none' : 2,
                    'hidden' : 2,
                    'dotted' : 2,
                    'dashed' : 2,
                    'solid' : 2,
                    'double' : 2,
                    'groove' : 2,
                    'ridge' : 2,
                    'inset' : 2,
                    'outset' : 2
                },
                'groove' : {
                    'none' : 2,
                    'hidden' : 2,
                    'dotted' : 2,
                    'dashed' : 2,
                    'solid' : 2,
                    'double' : 2,
                    'groove' : 2,
                    'ridge' : 2,
                    'inset' : 2,
                    'outset' : 2
                },
                'ridge' : {
                    'none' : 2,
                    'hidden' : 2,
                    'dotted' : 2,
                    'dashed' : 2,
                    'solid' : 2,
                    'double' : 2,
                    'groove' : 2,
                    'ridge' : 2,
                    'inset' : 2,
                    'outset' : 2
                },
                'inset' : {
                    'none' : 2,
