(function(root){

/*
 * General Value Types definition
 * they return an object of arrays of type { <name>: [<start-value>, <end-value>], ... }
 */
var values = {
    'length' : function() {
        // http://www.w3.org/TR/css3-values/#lengths
        return {
            // CSS Values and Module Level 3
            // ch: ['1ch', '10ch'],
            // rem: ['1rem', '10rem'],
            // vw: ['1vw', '10vw'],
            // vh: ['1vh', '10vh'],
            // vmin: ['1vmin', '10vmin'],
            // vmax: ['1vmax', '10vmax'],
            // CSS Values and Module Level 2
            pt: ['1pt', '10pt'],
            pc: ['1pc', '10pc'],
            px: ['1px', '10px'],
            // CSS Values and Module Level 1
            em: ['1em', '10em'],
            ex: ['1ex', '10ex'],
            mm: ['1mm', '10mm'],
            cm: ['1cm', '10cm'],
            'in': ['1in', '10in']
        };
    },
    'length-em': function() {
        return {
            em: ['1.1em', '1.5em']
        };
    },
    'percentage': function() {
        // http://www.w3.org/TR/css3-values/#percentages
        return {
            '%': ['33%', '80%']
        };
    },
    'color': function() {
        // http://www.w3.org/TR/css3-values/#colors
        // http://www.w3.org/TR/css3-color/
        return {
            rgba: ['rgba(100,100,100,1)', 'rgba(10,10,10,0.4)']
        };
    },
    'rectangle': function() {
        // http://www.w3.org/TR/CSS2/visufx.html#value-def-shape
        return {
            rectangle: ['rect(10px,10px,10px,10px)', 'rect(15px,15px,5px,5px)']
        };
    },
    'font-weight': function() {
        // http://www.w3.org/TR/css3-fonts/#font-weight-prop
        return {
            keyword: ["normal", "bold"],
            numeric: ["100", "900"]
        };
    },
    'number': function() {
        // http://www.w3.org/TR/css3-values/#number
        return {
            integer: ["1", "10"],
            decimal: ["1.1", "9.55"]
        };
    },
    'number[0,1]': function() {
        // http://www.w3.org/TR/css3-values/#number
        // applies to [0,1]-ranged properties like opacity
        return {
            "zero-to-one": ["0.2", "0.9"]
        };
    },
    'integer': function() {
        // http://www.w3.org/TR/css3-values/#integer
        return {
            integer: ["1", "10"]
        };
    },
    'shadow': function() {
        // http://www.w3.org/TR/css-text-decor-3/#text-shadow-property
        return {
            shadow: ['rgba(0,0,0,0.1) 5px 6px 7px', 'rgba(10,10,10,0.9) 5px 6px 7px']
        };
    },
    'visibility': function() {
        // http://www.w3.org/TR/CSS2/visufx.html#visibility
        return {
            keyword: ['visible', 'hidden', {discrete: true}]
        };
    },
    'auto': function(property) {
        var types = properties[property] || unspecified_properties[property];
        var val = values[types[0]](property);
        var key = Object.keys(val).shift();
        return {
            to: [val[key][1], 'auto'],
            from: ['auto', val[key][1]]
        };
    },
    // types reqired for non-specified properties
    'border-radius': function() {
        return {
            px: ['1px', '10px'],
            "px-px": ['1px 3px', '10px 13px']
        };
    },
    'image' : function() {
        var prefix = getValueVendorPrefix('background-image', 'linear-gradient(top, hsl(0, 80%, 70%), #bada55)');
        return {
            // Chrome implements this
            url: ['url(support/one.gif)', 'url(support/two.gif)'],
            data: ['url(data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=)', 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==)'],
            // A hunch, as from the spec:
            // http://www.w3.org/TR/css3-transitions/#animatable-types
            // gradient: interpolated via the positions and colors of each stop. They must have the same type (radial or linear) and same number of stops in order to be animated. Note: [CSS3-IMAGES] may extend this definition.
            gradient: [prefix + 'linear-gradient(top, hsl(0, 80%, 70%), #bada55)', prefix + 'linear-gradient(top, #bada55, hsl(0, 80%, 70%))']
        };
    },
    'background-size': function() {
        return {
            keyword: ['cover', 'contain']
        };
    },
    'box-shadow': function() {
        // http://www.w3.org/TR/css3-background/#ltshadowgt
        return {
            shadow: ['60px -16px teal', '60px -16px red']
        };
    },
    'vertical': function() {
        return {
            keyword: ['top', 'bottom']
        };
    },
    'horizontal': function() {
        return {
            keyword: ['left', 'right']
        };
    },
    'font-stretch': function() {
        return {
            keyword: ['condensed', 'expanded']
        };
    },
    'transform': function() {
        return {
            rotate: ['rotate(10deg)', 'rotate(20deg)']
        };
    },
    'position': function() {
        return {
            'static to absolute': ['static', 'absolute', {discrete: true}],
            'relative to absolute': ['relative', 'absolute', {discrete: true}],
            'absolute to fixed': ['absolute', 'fixed', {discrete: true}]
        };
    },
    'display': function() {
        return {
            'static to absolute': ['none', 'block', {discrete: true}],
            'block to inline-block': ['block', 'inline-block', {discrete: true}]
        };
    }
};

/*
 * Property to Type table
 * (as stated in specification)
 */
var properties = {
    'background-color': ['color'],
    'background-position': ['length', 'percentage'],

    'border-top-width': ['length'],
    'border-right-width': ['length'],
    'border-bottom-width': ['length'],
    'border-left-width': ['length'],

    'border-top-color': ['color'],
    'border-right-color': ['color'],
    'border-bottom-color': ['color'],
    'border-left-color': ['color'],

    'padding-bottom': ['length'],
    'padding-left': ['length'],
    'padding-right': ['length'],
    'padding-top': ['length'],

    'margin-bottom': ['length'],
    'margin-left': ['length'],
    'margin-right': ['length'],
    'margin-top': ['length'],

    'height': ['length', 'percentage'],
    'width': ['length', 'percentage'],
    'min-height': ['length', 'percentage'],
    'min-width': ['length', 'percentage'],
    'max-height': ['length', 'percentage'],
    'max-width': ['length', 'percentage'],

    'top': ['length', 'percentage'],
    'right': ['length', 'percentage'],
    'bottom': ['length', 'percentage'],
    'left': ['length', 'percentage'],

    'color': ['color'],
    'font-size': ['length', 'percentage'],
    'font-weight': ['font-weight'],
    'line-height': ['number', 'length', 'percentage'],
    'letter-spacing': ['length'],
    // Note: percentage is Level3 and not implemented anywhere yet
    // https://drafts.csswg.org/css3-text/#word-spacing
    'word-spacing': ['length', 'percentage'],
    'text-indent': ['length', 'percentage'],
    'text-shadow': ['shadow'],

    'outline-color': ['color'],
    // outline-offset <integer> used to be an error in the spec
    'outline-offset': ['length'],
    'outline-width': ['length'],

    'clip': ['rectangle'],
    // Note: doesn't seem implemented anywhere
    'crop': ['rectangle'],

    'vertical-align': ['length', 'percentage'],
    'opacity': ['number[0,1]'],
    'visibility': ['visibility'],
    'z-index': ['integer']
};

/*
 * Property to auto-value mapping
 * (lazily taken from http://www.siliconbaytraining.com/pages/csspv.html)
 */
var properties_auto = [
    'margin-top',
    'margin-right',
    'margin-bottom',
    'margin-left',
    'height',
    'width',
    'clip',
    'marker-offset',
    'top',
    'right',
    'left',
    'bottom',
    'z-index'
];

/*
 * Property to Type table
 * (missing value-types of specified properties)
 */
var missing_properties = {
    'margin-bottom': ['percentage'],
    'margin-left': ['percentage'],
    'margin-right': ['percentage'],
    'margin-top': ['percentage'],
    'padding-bottom': ['percentage'],
    'padding-left': ['percentage'],
    'padding-right': ['percentage'],
    'padding-top': ['percentage'],
    'vertical-align': ['vertical']
};

/*
 * Property to Type table
 * (properties that haven't been specified but implemented)
 */
var unspecified_properties = {
    // http://oli.jp/2010/css-animatable-properties/
    'border-top-left-radius': ['border-radius'],
    'border-top-right-radius': ['border-radius'],
    'border-bottom-left-radius': ['border-radius'],
    'border-bottom-right-radius': ['border-radius'],
    'background-image': ['image'],
    'background-size': ['background-size'],
    // https://drafts.csswg.org/css3-background/#the-box-shadow
    // Animatable:   yes, except between inner and outer shadows (Transition to/from an absent shadow is a transition to/from ‘0 0 transparent’ or ‘0 0 transparent inset’, as appropriate.)
    'box-shadow': ['box-shadow'],
    'font-size-adjust': ['number'],
    'font-stretch': ['font-stretch'],
    'marker-offset': ['length'],
    'text-decoration-color': ['color'],
    'column-count': ['integer'],
    'column-gap': ['length'],
    'column-rule-color': ['color'],
    'column-rule-width': ['length'],
    'column-width': ['length'],
    'transform': ['transform'],
    'transform-origin': ['horizontal'],
    'zoom': ['number'],
    'outline-radius-topleft': ['length', 'percentage'],
    'outline-radius-topright': ['length', 'percentage'],
    'outline-radius-bottomright': ['length', 'percentage'],
    'outline-radius-bottomleft': ['length', 'percentage'],
    'display': ['display'],
    'position': ['position']
};

/*
 * additional styles required to actually render
 * (different browsers expect different environment)
 */
var additional_styles = {
    // all browsers
    'border-top-width': {'border-top-style' : 'solid'},
    'border-right-width': {'border-right-style' : 'solid'},
    'border-bottom-width': {'border-bottom-style' : 'solid'},
    'border-left-width': {'border-left-style' : 'solid'},
    'top': {'position': 'absolute'},
    'right': {'position': 'absolute'},
    'bottom': {'position': 'absolute'},
    'left': {'position': 'absolute'},
    'z-index': {'position': 'absolute'},
    'outline-offset': {'outline-style': 'solid'},
    'outline-width': {'outline-style': 'solid'},
    'word-spacing': {'width': '100px', 'height': '100px'},
    // unspecified properties
    'column-rule-width': {'column-rule-style': 'solid'},
    'position': {'width': '50px', 'height': '50px', top: '10px', left: '50px'}
};

/*
 * additional styles required *on the parent* to actually render
 * (different browsers expect different environment)
 */
var parent_styles = {
    'border-top-width': {'border-top-style' : 'solid'},
    'border-right-width': {'border-right-style' : 'solid'},
    'border-bottom-width': {'border-bottom-style' : 'solid'},
    'border-left-width': {'border-left-style' : 'solid'},
    'height': {'width': '100px', 'height': '100px'},
    'min-height': {'width': '100px', 'height': '100px'},
    'max-height': {'width': '100px', 'height': '100px'},
    'width': {'width': '100px', 'height': '100px'},
    'min-width': {'width': '100px', 'height': '100px'},
    'max-width': {'width': '100px', 'height': '100px'},
    // unspecified properties
    'position': {'position': 'relative', 'width': '100px', 'height': '100px'},
    // inheritance tests
    'top': {'width': '100px', 'height': '100px', 'position': 'relative'},
    'right': {'width': '100px', 'height': '100px', 'position': 'relative'},
    'bottom': {'width': '100px', 'height': '100px', 'position': 'relative'},
    'left': {'width': '100px', 'height': '100px', 'position': 'relative'}
};


function assemble(props) {
    var tests = [];

    // assemble tests
    for (var property in props) {
        props[property].forEach(function(type) {
            var _values = values[type](property);
            Object.keys(_values).forEach(function(unit) {
                var data = {
                    name: property + ' ' + type + '(' + unit + ')',
                    property: property,
                    valueType : type,
                    unit : unit,
                    parentStyle: extend({}, parent_styles[property] || {}),
                    from: extend({}, additional_styles[property] || {}),
                    to: {}
                };

                data.from[property] = _values[unit][0];
                data.to[property] = _values[unit][1];
                data.flags = _values[unit][2] || {};

                tests.push(data);
            });
        });
    }

    return tests;
}

root.getPropertyTests = function() {
    return assemble(properties);
};

root.getMissingPropertyTests = function() {
    return assemble(missing_properties);
};

root.getUnspecifiedPropertyTests = function() {
    return assemble(unspecified_properties);
};

root.getFontSizeRelativePropertyTests = function() {
    var accepted = {};

    for (var key in properties) {
        if (!Object.prototype.hasOwnProperty.call(properties, key) || key === "font-size") {
            continue;
        }

        if (properties[key].indexOf('length') > -1) {
            accepted[key] = ['length-em'];
        }
    }

    return assemble(accepted);
};

root.getAutoPropertyTests = function() {
    var accepted = {};

    for (var i = 0, key; key = properties_auto[i]; i++) {
        accepted[key] = ['auto'];
    }

    return assemble(accepted);
};

root.filterPropertyTests = function(tests, names) {
    var allowed = {};
    var accepted = [];

    if (typeof names === "string") {
        names = [names];
    }

    if (!(names instanceof RegExp)) {
        names.forEach(function(name) {
            allowed[name] = true;
        });
    }

    tests.forEach(function(test) {
        if (names instanceof RegExp) {
            if (!test.name.match(names)) {
                return;
            }
        } else if (!allowed[test.name]) {
            return;
        }

        accepted.push(test);
    });

    return accepted;
};

})(window);
