/**
 * 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.
 *
 * Copyright 2002-2019, Carrot Search s.c, All Rights Reserved.
 *
 *
 * Debugging assertions for Carrot Search FoamTree.
 */
(function() {
  /**
   * Option constraints.
   */
  var optionDefinitions = function() {
    var args = {};

    // Static
    args["supported"] = {
      type: "boolean",
      asserts: ReadOnly(),
      static: true
    };
    args["geometry"] = {
      type: "object",
      asserts: ReadOnly(),
      static: true
    };

    [
      "geometry.boundingBox",
      "geometry.polygonCentroid",
      "geometry.rectangleInPolygon",
      "geometry.circleInPolygon",
      "geometry.stabPolygon",
      "context.roundRect",
      "context.fillPolygonWithText",
      "context.scratch",
      "context.replay"
    ].forEach(function (key) {
      args[key] = {
        type: "function",
        asserts: ReadOnly(),
        static: true
      }
    });

    // Special.
    args["dataObject"] = {
      type: "object",
      asserts: Or(IsObject(), IsNull(), IsUndefined())
    };

    args["imageData"] = {
      type: "string",
      asserts: ReadOnly()
    };

    args["viewport"] = {
      type: "object",
      asserts: ReadOnly()
    };

    ([
      "hierarchy",
      "geometry",
      "containerCoordinates",
      "state"
    ]).forEach(function(key) {
        args[key] = {
          type: "object",
          asserts: ReadOnly()
        };
      });

    args["times"] = {
      type: "object",
      asserts: ReadOnly()
    };

    ([
      "selection",
      "open",
      "exposure",
      "attach"
    ]).forEach(function(key) {
        args[key] = {
          type: "mixed"
        };
      });

    // Unconstrained strings
    ([
      "attributionText",
      "attributionLogo",
      "attributionUrl",
      "groupLabelFontFamily",
      "groupLabelFontStyle",
      "groupLabelFontWeight",
      "groupLabelFontVariant",
      "titleBarFontFamily",
      "titleBarFontStyle",
      "titleBarFontWeight",
      "titleBarFontVariant"
    ]).forEach(function(key) {
        args[key] = {
          type: "string",
          asserts: Or(IsString(), IsNull(), IsUndefined())
        };
      });

    // Element identifier
    ([
      "id"
    ]).forEach(function(key) {
        args[key] = {
          type: "string",
          asserts: And(IsString(), IdentifiesExistingElement())
        };
      });

    // DOM element
    ([
      "element"
    ]).forEach(function(key) {
        args[key] = {
          type: "object",
          asserts: IsElement()
        };
      });

    // Booleans
    ([
      "logging",
      "layoutByWeightOrder",
      "relaxationVisible",
      "showZeroWeightGroups",
      "parentOpacityBalancing",
      "descriptionGroupPolygonDrawn",
      "androidStockBrowserWorkaround"
    ]).forEach(function(key) {
        args[key] = {
          type: "boolean",
          asserts: IsBoolean()
        };
      });

    // enums
    args["layout"] = {
      type: "string",
      asserts: And(NotEmpty(), OneOf([
        "relaxed", "ordered", "squarified"
      ]))
    };

    args["stacking"] = {
      type: "string",
      asserts: And(NotEmpty(), OneOf([
        "hierarchical", "flattened"
      ]))
    };
    args["descriptionGroup"] = {
      type: "string",
      asserts: And(NotEmpty(), OneOf([
        "auto", "always"
      ]))
    };
    args["descriptionGroupType"] = {
      type: "string",
      asserts: And(NotEmpty(), OneOf([
        "floating", "stab"
      ]))
    };

    ([
      "initializer", // alias
      "relaxationInitializer"
    ]).forEach(function(key) {
        args[key] = {
          type: "string",
          asserts: And(NotEmpty(), OneOf([
            "fisheye", "squarified", "blackhole", "ordered", "random",

            // Deprecated
            "order", "treemap"
          ]))
        };
      });

    args["groupContentDecoratorTriggering"] = {
      type: "string",
      asserts: And(NotEmpty(), OneOf([
        "onShapeDirty", "onSurfaceDirty"
      ]))
    };

    ([
      "groupGrowingEasing",
      "exposeEasing",
      "rolloutEasing",
      "pullbackEasing",
      "fadeEasing",
      "zoomMouseWheelEasing"
    ]).forEach(function(key) {
        args[key] = {
          type: "string",
          asserts: And(NotEmpty(), OneOf([
            "linear", "bounce",
            "squareIn", "squareOut", "squareInOut",
            "cubicIn", "cubicOut", "cubicInOut",
            "quadIn", "quadOut", "quadInOut"
          ]))
        };
      });

    ([
      "groupFillType",
      "groupStrokeType"
    ]).forEach(function(key) {
        args[key] = {
          type: "string",
          asserts: And(NotEmpty(), OneOf([
            "none", "plain", "gradient"
          ]))
        };
      });

    args["rainbowColorDistribution"] = {
      type: "string",
      asserts: And(NotEmpty(), OneOf([
        "radial", "linear"
      ]))
    };

    args["interactionHandler"] = {
      type: "string",
      asserts: And(NotEmpty(), OneOf([
        "hammerjs", "builtin"
      ]))
    };

    ([
      "wireframeLabelDrawing",
      "wireframeContentDecorationDrawing"
    ]).forEach(function (key) {
        args[key] = {
          type: "string",
          asserts: And(NotEmpty(), OneOf([
            "auto", "always", "never"
          ]))
        };
      });

    args["incrementalDraw"]  = {
      type: "string",
      asserts: And(NotEmpty(), OneOf([
        "none", "accurate", "fast"
      ]))
    };

    ([
      "rolloutStartPoint",
      "pullbackStartPoint"
    ]).forEach(function(key) {
        args[key] = {
          type: "string",
          asserts: And(NotEmpty(), OneOf([
            "center", "topleft", "bottomright", "random"
          ]))
        };
      });

    ([
      "rolloutMethod",
      "pullbackMethod"
    ]).forEach(function(key) {
        args[key] = {
          type: "string",
          asserts: And(NotEmpty(), OneOf([
            "groups", "individual"
          ]))
        };
      });


    // CSS colors
    ([
      "groupSelectionOutlineColor",
      "groupSelectionOutlineShadowColor",
      "groupExposureShadowColor",
      "rainbowStartColor",
      "rainbowEndColor",
      "titleBarBackgroundColor",
      "titleBarTextColor",
      "groupLabelDarkColor",
      "groupLabelLightColor"
    ]).forEach(function(key) {
        args[key] = {
          type: "string",
          asserts: And(NotEmpty(), CssColor())
        }
      });

    // Functions
    ([
      "groupColorDecorator",
      "groupLabelDecorator",
      "groupLabelLayoutDecorator",
      "groupContentDecorator",
      "titleBarDecorator"
    ]).forEach(function(key) {
        args[key] = {
          type: "function",
          asserts: IsFunction()
        }
      });

    // Callbacks
    ([
      "onModelChanging", "onModelChanged", "onRolloutStart", "onRolloutComplete",
      "onRelaxationStep", "onRedraw", "onViewReset",

      "onGroupOpenOrCloseChanging", "onGroupOpenOrCloseChanged",
      "onGroupExposureChanging", "onGroupExposureChanged",
      "onGroupSelectionChanging", "onGroupSelectionChanged",

      "onGroupHover", "onGroupClick", "onGroupDoubleClick", "onGroupHold",
      "onGroupMouseDown", "onGroupMouseUp","onGroupMouseMove", "onGroupMouseWheel",
      "onGroupDragStart", "onGroupDrag", "onGroupDragEnd",
      "onGroupTransformStart", "onGroupTransform", "onGroupTransformEnd",

      "onKeyUp"
    ]).forEach(function(key) {
        args[key] = {
          type: "function | Array",
          asserts: Or(IsFunction(), IsArrayOfFunctions())
        }
      });

    ([
      "groupLabelLineHeight",
      "groupExposureScale",
      "zoomMouseWheelFactor"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), IsNumeric("[1,∞)"))
        }
      });

    ([
      "groupBorderRadius",
      "groupGrowingDrag",
      "groupUnexposureLabelColorThreshold",
      "rainbowLightnessShiftCenter",
      "parentFillOpacity",
      "parentStrokeOpacity",
      "parentLabelOpacity",
      "groupLabelMaxTotalHeight",
      "groupLabelUpdateThreshold",
      "groupLabelColorThreshold",
      "rolloutTransformationCenter",
      "rolloutPolygonDrag",
      "rolloutPolygonDuration",
      "rolloutLabelDelay",
      "rolloutLabelDrag",
      "rolloutLabelDuration",
      "rolloutChildGroupsDrag",
      "rolloutChildGroupsDelay",
      "pullbackTransformationCenter",
      "pullbackPolygonDelay",
      "pullbackPolygonDrag",
      "pullbackPolygonDuration",
      "pullbackLabelDelay",
      "pullbackLabelDrag",
      "pullbackLabelDuration",
      "pullbackChildGroupsDelay",
      "pullbackChildGroupsDrag",
      "pullbackChildGroupsDuration",
      "attributionLogoScale",
      "descriptionGroupDistanceFromCenter",
      "descriptionGroupSize",
      "descriptionGroupMaxHeight",
      "attributionDistanceFromCenter"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), IsNumeric("[0,1]"))
        }
      });

    ([
      "rainbowLightnessCorrection",
      "rainbowSaturationCorrection"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), IsNumeric("[-1,1]"))
        }
      });

    ([
      "rectangleAspectRatioPreference",
      "rolloutScalingStrength",
      "rolloutTranslationXStrength",
      "rolloutTranslationYStrength",
      "rolloutRotationStrength",
      "pullbackScalingStrength",
      "pullbackTranslationXStrength",
      "pullbackTranslationYStrength",
      "pullbackRotationStrength"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), IsNumeric("(-∞,∞)"))
        }
      });

    ([
      "pixelRatio",
      "wireframePixelRatio",
      "relaxationMaxDuration",
      "relaxationQualityThreshold",
      "groupMinDiameter",
      "maxGroups",
      "maxGroupLevelsAttached",
      "maxGroupLevelsDrawn",
      "maxGroupLabelLevelsDrawn",
      "groupGrowingDuration",
      "groupResizingBudget",
      "groupBorderWidth",
      "groupBorderWidthScaling",
      "groupInsetWidth",
      "groupBorderRadiusCorrection",
      "groupSelectionOutlineWidth",
      "groupSelectionOutlineShadowSize",
      "groupFillGradientRadius",
      "groupStrokeWidth",
      "groupStrokeGradientRadius",
      "groupExposureShadowSize",
      "groupExposureZoomMargin",
      "exposeDuration",
      "openCloseDuration",
      "wireframeDrawMaxDuration",
      "wireframeToFinalFadeDuration",
      "wireframeToFinalFadeDelay",
      "finalCompleteDrawMaxDuration",
      "finalIncrementalDrawMaxDuration",
      "finalToWireframeFadeDuration",
      "groupLabelHorizontalPadding",
      "groupLabelVerticalPadding",
      "groupLabelMinFontSize",
      "groupLabelMaxFontSize",
      "rolloutDuration",
      "pullbackDuration",
      "fadeDuration",
      "zoomMouseWheelDuration",
      "maxLabelSizeForTitleBar",
      "titleBarMinFontSize",
      "titleBarMaxFontSize",
      "titleBarTextPaddingLeftRight",
      "titleBarTextPaddingTopBottom",
      "descriptionGroupMinHeight",
      "attributionWeight"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), IsNumeric("[0,∞)"))
        }
      });

    ([
      "groupSelectionFillHueShift",
      "groupSelectionStrokeHueShift",
      "groupFillGradientCenterHueShift",
      "groupFillGradientRimHueShift",
      "groupStrokePlainHueShift",
      "groupStrokeGradientUpperHueShift",
      "groupStrokeGradientLowerHueShift",
      "groupHoverFillHueShift",
      "groupHoverStrokeHueShift",
      "rainbowColorDistributionAngle",
      "rainbowLightnessDistributionAngle"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), IsNumeric("[-180,180]"))
        }
      });

    ([
      "groupSelectionFillSaturationShift",
      "groupSelectionFillLightnessShift",
      "groupSelectionStrokeSaturationShift",
      "groupSelectionStrokeLightnessShift",
      "groupFillGradientCenterSaturationShift",
      "groupFillGradientCenterLightnessShift",
      "groupFillGradientRimSaturationShift",
      "groupFillGradientRimLightnessShift",
      "groupStrokePlainSaturationShift",
      "groupStrokePlainLightnessShift",
      "groupStrokeGradientUpperSaturationShift",
      "groupStrokeGradientUpperLightnessShift",
      "groupStrokeGradientLowerSaturationShift",
      "groupStrokeGradientLowerLightnessShift",
      "groupHoverFillSaturationShift",
      "groupHoverFillLightnessShift",
      "groupHoverStrokeSaturationShift",
      "groupHoverStrokeLightnessShift",
      "rainbowLightnessShift",
      "groupUnexposureLightnessShift",
      "groupUnexposureSaturationShift"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), IsNumeric("[-100,100]"))
        }
      });


    ([
      "groupStrokeGradientAngle"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), IsNumeric("[0,180]"))
        }
      });

    ([
      "descriptionGroupPosition",
      "attributionPosition"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), Or(IsNumeric("[0,360)"), OneOf([
            "topleft", "bottomright", "random"
          ])))
        }
      });

    ([
      "attributionTheme"
    ]).forEach(function(key) {
        args[key] = {
          type: "number",
          asserts: And(NotEmpty(), OneOf([
            "light", "dark"
          ]))
        }
      });

    // API changes
    ([
      "supported",
      "id",
      "element",
      "pixelRatio",
      "wireframePixelRatio",

      "dataObject",
      "relaxationInitializer",
      "relaxationMaxDuration",
      "relaxationVisible",
      "relaxationQualityThreshold",
      "groupMinDiameter",
      "showZeroWeightGroups",
    
      "groupGrowingDuration",
      "groupGrowingEasing",
      "groupGrowingDrag",
      "groupResizingBudget",
    
      "groupBorderRadius",
      "groupBorderWidth",
      "groupBorderWidthScaling",
      "groupInsetWidth",
      "groupBorderRadiusCorrection",

      "groupSelectionOutlineWidth",
      "groupSelectionOutlineColor",
      "groupSelectionOutlineShadowSize",
      "groupSelectionOutlineShadowColor",
      "groupSelectionFillHueShift",
      "groupSelectionFillSaturationShift",
      "groupSelectionFillLightnessShift",
      "groupSelectionStrokeHueShift",
      "groupSelectionStrokeSaturationShift",
      "groupSelectionStrokeLightnessShift",
    
      "groupFillType",
      "groupFillGradientRadius",
      "groupFillGradientCenterHueShift",
      "groupFillGradientCenterSaturationShift",
      "groupFillGradientCenterLightnessShift",
      "groupFillGradientRimHueShift",
      "groupFillGradientRimSaturationShift",
      "groupFillGradientRimLightnessShift",
    
      "groupStrokeType",
      "groupStrokeWidth",
      "groupStrokePlainHueShift",
      "groupStrokePlainSaturationShift",
      "groupStrokePlainLightnessShift",
      "groupStrokeGradientRadius",
      "groupStrokeGradientAngle",
      "groupStrokeGradientUpperHueShift",
      "groupStrokeGradientUpperSaturationShift",
      "groupStrokeGradientUpperLightnessShift",
      "groupStrokeGradientLowerHueShift",
      "groupStrokeGradientLowerSaturationShift",
      "groupStrokeGradientLowerLightnessShift",
    
      "groupHoverFillHueShift",
      "groupHoverFillSaturationShift",
      "groupHoverFillLightnessShift",
      "groupHoverStrokeHueShift",
      "groupHoverStrokeSaturationShift",
      "groupHoverStrokeLightnessShift",
    
      "groupExposureScale",
      "groupExposureShadowColor",
      "groupExposureShadowSize",
      "groupExposureZoomMargin",
      "groupUnexposureLightnessShift",
      "groupUnexposureSaturationShift",
      "groupUnexposureLabelColorThreshold",
      "exposeDuration",
      "exposeEasing",

      "groupLabelDecorator",
      "groupColorDecorator",
      "titleBarDecorator",

      "openCloseDuration",
    
      "rainbowStartColor",
      "rainbowEndColor",
      "rainbowColorDistribution",
      "rainbowColorDistributionAngle",
      "rainbowLightnessDistributionAngle",
      "rainbowSaturationCorrection",
      "rainbowLightnessCorrection",
      "rainbowLightnessShift",
      "rainbowLightnessShiftCenter",
    
      "parentFillOpacity",
      "parentStrokeOpacity",
      "parentLabelOpacity",
      "parentOpacityBalancing",
    
      "wireframeDrawMaxDuration",
      "wireframeLabelDrawing",
      "wireframeToFinalFadeDuration",
      "wireframeToFinalFadeDelay",
      "finalCompleteDrawMaxDuration",
      "finalIncrementalDrawMaxDuration",
      "finalToWireframeFadeDuration",
      "androidStockBrowserWorkaround",
    
      "groupLabelFontFamily",
      "groupLabelLineHeight",
      "groupLabelHorizontalPadding",
      "groupLabelVerticalPadding",
      "groupLabelMinFontSize",
      "groupLabelMaxFontSize",
      "groupLabelMaxTotalHeight",
      "groupLabelUpdateThreshold",
      "groupLabelDarkColor",
      "groupLabelLightColor",
      "groupLabelColorThreshold",
    
      "titleBarFontFamily",
      "titleBarMinFontSize",
      "titleBarMaxFontSize",
      "titleBarBackgroundColor",
      "titleBarTextColor",
      "titleBarTextPaddingLeftRight",
      "titleBarTextPaddingTopBottom",
      "maxLabelSizeForTitleBar",

      "rolloutStartPoint",
      "rolloutEasing",
      "rolloutMethod",
      "rolloutDuration",
      "rolloutScalingStrength",
      "rolloutTranslationXStrength",
      "rolloutTranslationYStrength",
      "rolloutRotationStrength",
      "rolloutTransformationCenter",
      "rolloutPolygonDrag",
      "rolloutPolygonDuration",
      "rolloutLabelDelay",
      "rolloutLabelDrag",
      "rolloutLabelDuration",
      "rolloutChildGroupsDrag",
      "rolloutChildGroupsDelay",
    
      "pullbackStartPoint",
      "pullbackEasing",
      "pullbackMethod",
      "pullbackDuration",
      "pullbackScalingStrength",
      "pullbackTranslationXStrength",
      "pullbackTranslationYStrength",
      "pullbackRotationStrength",
      "pullbackTransformationCenter",
      "pullbackPolygonDelay",
      "pullbackPolygonDrag",
      "pullbackPolygonDuration",
      "pullbackLabelDelay",
      "pullbackLabelDrag",
      "pullbackLabelDuration",
      "pullbackChildGroupsDelay",
      "pullbackChildGroupsDrag",
      "pullbackChildGroupsDuration",
    
      "fadeDuration",
      "fadeEasing",
    
      "zoomMouseWheelFactor",
      "zoomMouseWheelDuration",
      "zoomMouseWheelEasing",
    
      "attributionText",
      "attributionLogo",
      "attributionUrl",
    
      "interactionHandler",
    
      "onModelChanged",
      "onRelaxationStep",
    
      "onModelChanging",
      "onViewReset",
      "onGroupOpenOrCloseChanging",
      "onGroupOpenOrCloseChanged",
      "onGroupExposureChanging",
      "onGroupExposureChanged",
      "onGroupSelectionChanging",
      "onGroupSelectionChanged",
      "onRolloutStart",
      "onRolloutComplete",
      "onRedraw",

      "onGroupClick",
      "onGroupDoubleClick",
      "onGroupHold",
      "onGroupHover",
      "onGroupMouseWheel",
      "onGroupMouseDown",
      "onGroupDragStart",
      "onGroupDrag",
      "onGroupDragEnd",
      "onGroupTransformStart",
      "onGroupTransform",
      "onGroupTransformEnd",
      "onKeyUp",
    
      "selection",
      "open",
      "exposure",

      "hierarchy",
      "geometry",
      "state",

      "imageData",
      "logging",
      "times"
    ]).forEach(function(key) {
      args[key].since = "3.0.0";
    });

    ([
      "groupContentDecorator",
      "groupContentDecoratorTriggering",
      "wireframeContentDecorationDrawing",
      "geometry.boundingBox",
      "geometry.polygonCentroid",
      "geometry.rectangleInPolygon",
      "geometry.circleInPolygon",
      "geometry.stabPolygon",
      "context.roundRect",
      "context.fillPolygonWithText",
      "context.scratch",
      "context.replay"
    ]).forEach(function(key) {
      args[key].since = "3.1.0";
    });

    ([
      "maxGroups",
      "maxGroupLevelsDrawn",
      "maxGroupLabelLevelsDrawn"
    ]).forEach(function(key) {
      args[key].since = "3.2.0";
    });

    ([
      "attributionPosition",
      "attributionDistanceFromCenter"
    ]).forEach(function(key) {
      args[key].since = "3.2.1";
    });

    ([
      "groupLabelFontStyle",
      "groupLabelFontWeight",
      "groupLabelFontVariant",
      "titleBarFontStyle",
      "titleBarFontWeight",
      "titleBarFontVariant"
    ]).forEach(function(key) {
      args[key].since = "3.2.2";
    });

    ([
      "viewport"
    ]).forEach(function(key) {
      args[key].since = "3.2.3";
    });

    ([
      "relaxationInitializer",
      "layout",
      "layoutByWeightOrder",
      "rectangleAspectRatioPreference"
    ]).forEach(function(key) {
      args[key].since = "3.3.0";
    });

    ([
      "attributionWeight"
    ]).forEach(function(key) {
      args[key].since = "3.3.1";
    });

    ([
      "stacking",
      "descriptionGroupType",
      "descriptionGroupSize",
      "descriptionGroupMinHeight",
      "descriptionGroupMaxHeight",
      "descriptionGroupPosition",
      "descriptionGroupDistanceFromCenter",
      "attributionLogoScale",
      "attributionTheme",
      "groupLabelLayoutDecorator",
      "descriptionGroupPolygonDrawn",
      "incrementalDraw"
    ]).forEach(function(key) {
      args[key].since = "3.4.0";
    });

    ([
      "onGroupMouseMove",
      "onGroupMouseUp",
      "containerCoordinates"
    ]).forEach(function(key) {
      args[key].since = "3.4.4";
    });

    ([
      "maxGroupLevelsAttached"
    ]).forEach(function(key) {
      args[key].since = "3.4.5";
    });

    ([
      "descriptionGroup"
    ]).forEach(function(key) {
      args[key].since = "3.4.9";
    });

    ([
      "initializer"
    ]).forEach(function(key) {
      args[key].until = "3.5.0";
    });

    return args;
  };


  /**
   * Constraint definitions.
   */

  function valueOf(v) {
    if (typeof v == "function") {
      return "[function]";
    } else {
      return "'" + v + "'";
    }
  }


  var NotEmpty = (function() {
    function NotEmpty() {
      if (this === window) return new NotEmpty();
    }

    NotEmpty.prototype.validate = function(value) {
      if ((typeof value == "undefined") || value == null || ("" + value) === "") {
        throw valueOf(value) + " is empty";
      }
    };

    NotEmpty.prototype.toString = function() {
      return "value is not empty";
    };

    return NotEmpty;
  })();


  var IsNull = (function() {
    function IsNull() {
      if (this === window) return new IsNull();
    }

    IsNull.prototype.validate = function(value) {
      if (value !== null) {
        throw valueOf(value) + " is not null";
      }
    };

    IsNull.prototype.toString = function() {
      return "value is null"
    };

    return IsNull;
  })();


  var IsUndefined = (function() {
    function IsUndefined() {
      if (this === window) return new IsUndefined();
    }

    IsUndefined.prototype.validate = function(value) {
      if (typeof value !== "undefined") {
        throw valueOf(value) + " is not undefined";
      }
    };

    IsUndefined.prototype.toString = function() {
      return "value is undefined";
    };

    return IsUndefined;
  })();


  var IsObject = (function() {
    function IsObject() {
      if (this === window) return new IsObject();
    }

    IsObject.prototype.validate = function(value) {
      if (value !== Object(value)) {
        throw valueOf(value) + " is not an object";
      }
    };

    IsObject.prototype.toString = function() {
      return "value is an object";
    };

    return IsObject;
  })();


  var ReadOnly = (function() {
    function ReadOnly() {
      if (this === window) return new ReadOnly();
    }

    ReadOnly.prototype.validate = function(value) {
      throw "option is read-only";
    };

    ReadOnly.prototype.toString = function() {
      return "option is read-only";
    };

    return ReadOnly;
  })();


  var IsNumeric = (function() {
    function IsNumeric(range) {
      if (this === window) return new IsNumeric(range);

      function inclusiveBracket(v) {
        switch (v) {
          case '[':
          case ']':
            return true;
          case '(':
          case ')':
            return false;
          default:
            throw "Unrecognized bracket op: " + v;
        }
      }

      function parseRange(v) {
        if (v == "∞")  { return Number.POSITIVE_INFINITY; }
        if (v == "-∞") { return Number.NEGATIVE_INFINITY; }
        if (isNaN(parseFloat(v))) throw "Not a number in range: " + v;
        return parseFloat(v);
      }

      // Simplistic range parsing.
      if (range) {
        // "(x,y)" => ["(", "0", ",", "∞", ")"]
        var comps = range.replace(/[\[\]\(\),]/g, " $& ").trim().split(/\s+/);
        this.left = parseRange(comps[1]);
        this.leftInclusive = inclusiveBracket(comps[0]);
        this.right = parseRange(comps[3]);
        this.rightInclusive = inclusiveBracket(comps[4]);
        this.range = range.replace(/∞/g, "infinity");
      }
    }

    IsNumeric.prototype.validate = function(value) {
      if (isNaN(parseFloat(value))) {
        throw valueOf(value) + " is not a number";
      }

      if (!isFinite(value)) {
        throw valueOf(value) + " is not a finite number";
      }

      if (this.range) {
        if ((value < this.left  || (value == this.left  && !this.leftInclusive)) ||
            (value > this.right || (value == this.right && !this.rightInclusive))) {
          throw valueOf(value) + " is not within " + this.range;
        }
      }
    };

    IsNumeric.prototype.toString = function() {
      if (this.range) {
        return "value is a number in range " + this.range;
      } else {
        return "value is a number";
      }
    };

    return IsNumeric;
  })();


  var IsString = (function() {
    function IsString() {
      if (this === window) return new IsString();
    }

    IsString.prototype.validate = function(value) {
      var toString = Object.prototype.toString;
      if (value != null && toString.call(value) != "[object String]") {
        throw valueOf(value) + " is not a string";
      }
    };

    IsString.prototype.toString = function() {
      return "value is a string";
    };

    return IsString;
  })();


  var IsBoolean = (function() {
    function IsBoolean() {
      if (this === window) return new IsBoolean();
    }

    IsBoolean.prototype.validate = function(value) {
      if (value != null && typeof value !== "undefined") {
        if (value !== true && value !== false) {
          throw valueOf(value) + " is not a boolean";
        }
      }
    };

    IsBoolean.prototype.toString = function() {
      return "value is a boolean";
    };

    return IsBoolean;
  })();

  var IsFunction = (function() {
    function IsFunction() {
      if (this === window) return new IsFunction();
    }

    IsFunction.prototype.validate = function(value) {
      if (value != null && value != undefined) {
        if (typeof value !== "function") {
          throw valueOf(value) + " [" + (typeof value) + "] is not a function";
        }
      }
    };

    IsFunction.prototype.toString = function() {
      return "value is a function";
    };

    return IsFunction;
  })();

  var IsArrayOfFunctions = (function() {
    function IsArrayOfFunctions() {
      if (this === window) return new IsArrayOfFunctions();
    }

    IsArrayOfFunctions.prototype.validate = function(value) {
      if (value != null && value != undefined) {
        var arrayOfFunctions = Array.isArray(value);
        if (arrayOfFunctions) {
          value.forEach(function(key) {
            if (typeof key !== "function") {
              arrayOfFunctions = false;
            }
          });
        }
        if (!arrayOfFunctions) {
          throw valueOf(value) + " [" + (typeof value) + "] is not an array of functions";
        }
      }
    };

    IsArrayOfFunctions.prototype.toString = function() {
      return "value is an array of functions";
    };

    return IsArrayOfFunctions;
  })();

  var Matches = (function() {
    function Matches(regexp) {
      if (this === window) { return new Matches(regexp); }
      this.regexp = regexp;
    }

    Matches.prototype.validate = function(value) {
      if (!this.regexp.test(value)) throw valueOf(value) + " does not match " + this.regexp;
    };

    Matches.prototype.toString = function() {
      return "value matches " + this.regexp;
    };

    return Matches;
  })();


  var CssColor = (function() {
    function CssColor() {
      if (this === window) { return new CssColor(); }
    }

    var predefinedColorsRegexp = new RegExp("^(AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenrod|DarkGray|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGray|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGray|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|Goldenrod|Gray|Green|GreenYellow|Honeydew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenrodYellow|LightGreen|LightGrey|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGray|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquamarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenrod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|Seashell|Sienna|Silver|SkyBlue|SlateBlue|SlateGray|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)$", "i");

    CssColor.prototype.validate = function(value) {
      if (/^rgba\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)\s*\)$/.test(value)) {
        return;
      }
      if (/^rgb\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)\s*\)$/.test(value)) {
        return;
      }
      if (/^hsla\(\s*([^,\s]+)\s*,\s*([^,%\s]+)%\s*,\s*([^,\s%]+)%\s*,\s*([^,\s]+)\s*\)$/.test(value)) {
        return;
      }
      if (/^hsl\(\s*([^,\s]+)\s*,\s*([^,\s%]+)%\s*,\s*([^,\s%]+)%\s*\)$/.test(value)) {
        return;
      }
      if (/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.test(value)) {
        return;
      }
      if (/^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/.test(value)) {
        return;
      }
      if (predefinedColorsRegexp.test(value)) {
        return;
      }

      throw valueOf(value) + " is not a CSS color specification";
    };

    CssColor.prototype.toString = function() {
      return "value is a CSS color";
    };

    return CssColor;
  })();


  var IdentifiesExistingElement = (function() {
    function IdentifiesExistingElement() {
      if (this === window) return new IdentifiesExistingElement();
    }

    IdentifiesExistingElement.prototype.validate = function(value) {
      var element = document.getElementById(value);
      if (!element) {
        throw valueOf(value) + " is not an identifier of an existing DOM element";
      }
    };

    IdentifiesExistingElement.prototype.toString = function() {
      return "value is an identifier of an existing DOM element";
    };

    return IdentifiesExistingElement;
  })();


  var IsElement = (function() {
    function IsElement() {
      if (this === window) return new IsElement();
    }

    IsElement.prototype.validate = function(value) {
      if (!(value instanceof HTMLElement)) {
        throw valueOf(value) + " is not a DOM element";
      }
    };

    IsElement.prototype.toString = function() {
      return "value is a DOM element";
    };

    return IsElement;
  })();


  var OneOf = (function() {
    function OneOf(values) {
      if (this === window) { return new OneOf(values); }
      this.values = values;
    }

    OneOf.prototype.validate = function(value) {
      if (this.values.indexOf(value) < 0) {
        throw valueOf(value) + " not one of [" + this.values.join(", ") + "]";
      }
    };

    OneOf.prototype.toString = function() {
      return "value one of [" + this.values.join(", ") + "]";
    };

    return OneOf;
  })();


  var Or = (function() {
    function Or() {
      if (this === window) { return Or.apply(new Or(), arguments); }
      this.clauses = Array.prototype.slice.call(arguments);
      return this;
    }

    Or.prototype.validate = function(value) {
      var vetoes = [];
      for (var i = 0; i < this.clauses.length; i++) {
        try {
          this.clauses[i].validate(value);
        } catch (e) {
          vetoes.push(e);
        }
      }

      if (vetoes.length == this.clauses.length) {
        throw vetoes.map(function(e) {return "(" + e + ")";}).join(" and ");
      }
    };

    Or.prototype.toString = function() {
      return this.clauses.map(function(e) {return "(" + e + ")";}).join(" or ");
    };

    return Or;
  })();


  var And = (function() {
    function And() {
      if (this === window) { return And.apply(new And(), arguments); }
      this.clauses = Array.prototype.slice.call(arguments);
      return this;
    }

    And.prototype.validate = function(value) {
      var vetoes = [];
      for (var i = 0; i < this.clauses.length; i++) {
        try {
          this.clauses[i].validate(value);
        } catch (e) {
          vetoes.push(e);
          break;  // fastpath, no need to evaluate further.
        }
      }

      if (vetoes.length != 0) {
        throw vetoes.map(function(e) {return "(" + e + ")";}).join(" and ");
      }
    };

    And.prototype.toString = function() {
      return this.clauses.map(function(e) {return "(" + e + ")";}).join(" and ");
    };

    return And;
  })();

  // Install or defer until FoamTree is loaded.
  var args = optionDefinitions();

  // Validate that there is an assert for each default option
  // and also that there are no asserts referring to non-existing options.
  var foamtreeClass = window["CarrotSearchFoamTree"];
  if (foamtreeClass) {
    var defaults = CarrotSearchFoamTree.defaults;
    for (var option in defaults) {
      if (defaults.hasOwnProperty(option)) {
        if (!args.hasOwnProperty(option)) {
          window.console.error("No assertion defined for option: " + option);
        }
      }
    }
    for (var assert in args) {
      if (args.hasOwnProperty(assert) && !args[assert].static && !args[assert].until) {
        if (!defaults.hasOwnProperty(assert)) {
          window.console.error("No option for assert: " + assert);
        }
      }
    }
  }

  function exists(option) {
    var exists = args.hasOwnProperty(option);
    if (!exists) {
      warn("option " + option + " does not exist.");
    }
    return exists;
  }

  var validator = {
    spec: function (option) {
      if (exists(option)) {
        return args[option];
      }
    },

    exists: exists,

    /**
     * Validates the provided options against the defined asserts,
     * removes invalid options from the object.
     *
     * @param opts
     */
    validate: function (opts, verbose) {
      for (var option in opts) {
        if (opts.hasOwnProperty(option)) {
          var assert = args[option];
          var valid = true;

          if (assert) {
            try {
              assert.asserts && assert.asserts.validate(opts[option]);
              if (verbose) {
                info("option " + option + " set to '" + opts[option] + "'.");
              }
            } catch (e) {
              warn("skipping incorrect value for option " + option + " " + e);
              valid = false;
            }
          } else {
            warn("skipping unknown option " + option);
            valid = false;
          }

          if (!valid) {
            delete opts[option];
          }
        }
      }
    }
  };

  if (foamtreeClass) {
    foamtreeClass["asserts"] = validator;
  } else {
    window["CarrotSearchFoamTree.asserts"] = validator;
  }


  function warn(message) {
    window.console.warn("FoamTree: " + message)
  }

  function info(message) {
    window.console.info("FoamTree: " + message)
  }
})();

/*
 * Build information
 * -----------------
 *
 * Build type    : Carrot Search FoamTree HTML5 (demo variant)
 * Build version : 3.4.10
 * Build number  : FOAMTREE-SOFTWARE4-DIST-78
 * Build time    : Nov 29, 2019
 * Built by      : bamboo
 * Build revision: bugfix/3.4.x/b18f7427
 */