var util = require('./util')

// http://www.w3.org/TR/css3-color/#html4
var BASIC_COLOR_KEYWORDS = {
  black: '#000000',
  silver: '#C0C0C0',
  gray: '#808080',
  white: '#FFFFFF',
  maroon: '#800000',
  red: '#FF0000',
  purple: '#800080',
  fuchsia: '#FF00FF',
  green: '#008000',
  lime: '#00FF00',
  olive: '#808000',
  yellow: '#FFFF00',
  navy: '#000080',
  blue: '#0000FF',
  teal: '#008080',
  aqua: '#00FFFF'
}

// http://www.w3.org/TR/css3-color/#svg-color
var EXTENDED_COLOR_KEYWORDS = {
  aliceblue: '#F0F8FF',
  antiquewhite: '#FAEBD7',
  aqua: '#00FFFF',
  aquamarine: '#7FFFD4',
  azure: '#F0FFFF',
  beige: '#F5F5DC',
  bisque: '#FFE4C4',
  black: '#000000',
  blanchedalmond: '#FFEBCD',
  blue: '#0000FF',
  blueviolet: '#8A2BE2',
  brown: '#A52A2A',
  burlywood: '#DEB887',
  cadetblue: '#5F9EA0',
  chartreuse: '#7FFF00',
  chocolate: '#D2691E',
  coral: '#FF7F50',
  cornflowerblue: '#6495ED',
  cornsilk: '#FFF8DC',
  crimson: '#DC143C',
  cyan: '#00FFFF',
  darkblue: '#00008B',
  darkcyan: '#008B8B',
  darkgoldenrod: '#B8860B',
  darkgray: '#A9A9A9',
  darkgreen: '#006400',
  darkgrey: '#A9A9A9',
  darkkhaki: '#BDB76B',
  darkmagenta: '#8B008B',
  darkolivegreen: '#556B2F',
  darkorange: '#FF8C00',
  darkorchid: '#9932CC',
  darkred: '#8B0000',
  darksalmon: '#E9967A',
  darkseagreen: '#8FBC8F',
  darkslateblue: '#483D8B',
  darkslategray: '#2F4F4F',
  darkslategrey: '#2F4F4F',
  darkturquoise: '#00CED1',
  darkviolet: '#9400D3',
  deeppink: '#FF1493',
  deepskyblue: '#00BFFF',
  dimgray: '#696969',
  dimgrey: '#696969',
  dodgerblue: '#1E90FF',
  firebrick: '#B22222',
  floralwhite: '#FFFAF0',
  forestgreen: '#228B22',
  fuchsia: '#FF00FF',
  gainsboro: '#DCDCDC',
  ghostwhite: '#F8F8FF',
  gold: '#FFD700',
  goldenrod: '#DAA520',
  gray: '#808080',
  green: '#008000',
  greenyellow: '#ADFF2F',
  grey: '#808080',
  honeydew: '#F0FFF0',
  hotpink: '#FF69B4',
  indianred: '#CD5C5C',
  indigo: '#4B0082',
  ivory: '#FFFFF0',
  khaki: '#F0E68C',
  lavender: '#E6E6FA',
  lavenderblush: '#FFF0F5',
  lawngreen: '#7CFC00',
  lemonchiffon: '#FFFACD',
  lightblue: '#ADD8E6',
  lightcoral: '#F08080',
  lightcyan: '#E0FFFF',
  lightgoldenrodyellow: '#FAFAD2',
  lightgray: '#D3D3D3',
  lightgreen: '#90EE90',
  lightgrey: '#D3D3D3',
  lightpink: '#FFB6C1',
  lightsalmon: '#FFA07A',
  lightseagreen: '#20B2AA',
  lightskyblue: '#87CEFA',
  lightslategray: '#778899',
  lightslategrey: '#778899',
  lightsteelblue: '#B0C4DE',
  lightyellow: '#FFFFE0',
  lime: '#00FF00',
  limegreen: '#32CD32',
  linen: '#FAF0E6',
  magenta: '#FF00FF',
  maroon: '#800000',
  mediumaquamarine: '#66CDAA',
  mediumblue: '#0000CD',
  mediumorchid: '#BA55D3',
  mediumpurple: '#9370DB',
  mediumseagreen: '#3CB371',
  mediumslateblue: '#7B68EE',
  mediumspringgreen: '#00FA9A',
  mediumturquoise: '#48D1CC',
  mediumvioletred: '#C71585',
  midnightblue: '#191970',
  mintcream: '#F5FFFA',
  mistyrose: '#FFE4E1',
  moccasin: '#FFE4B5',
  navajowhite: '#FFDEAD',
  navy: '#000080',
  oldlace: '#FDF5E6',
  olive: '#808000',
  olivedrab: '#6B8E23',
  orange: '#FFA500',
  orangered: '#FF4500',
  orchid: '#DA70D6',
  palegoldenrod: '#EEE8AA',
  palegreen: '#98FB98',
  paleturquoise: '#AFEEEE',
  palevioletred: '#DB7093',
  papayawhip: '#FFEFD5',
  peachpuff: '#FFDAB9',
  peru: '#CD853F',
  pink: '#FFC0CB',
  plum: '#DDA0DD',
  powderblue: '#B0E0E6',
  purple: '#800080',
  red: '#FF0000',
  rosybrown: '#BC8F8F',
  royalblue: '#4169E1',
  saddlebrown: '#8B4513',
  salmon: '#FA8072',
  sandybrown: '#F4A460',
  seagreen: '#2E8B57',
  seashell: '#FFF5EE',
  sienna: '#A0522D',
  silver: '#C0C0C0',
  skyblue: '#87CEEB',
  slateblue: '#6A5ACD',
  slategray: '#708090',
  slategrey: '#708090',
  snow: '#FFFAFA',
  springgreen: '#00FF7F',
  steelblue: '#4682B4',
  tan: '#D2B48C',
  teal: '#008080',
  thistle: '#D8BFD8',
  tomato: '#FF6347',
  turquoise: '#40E0D0',
  violet: '#EE82EE',
  wheat: '#F5DEB3',
  white: '#FFFFFF',
  whitesmoke: '#F5F5F5',
  yellow: '#FFFF00',
  yellowgreen: '#9ACD32'
}

var LENGTH_REGEXP = /^[-+]?\d*\.?\d+(\S*)$/
var SUPPORT_CSS_UNIT = ['px', 'pt', 'wx']

if(process.env.UNI_USING_NVUE_COMPILER){
  SUPPORT_CSS_UNIT.push('upx')
  SUPPORT_CSS_UNIT.push('rpx')
}

var ANYTHING_VALIDATOR = function ANYTHING_VALIDATOR(v) {
  return { value: v }
}

/**
 * the values below is valid
 * - number
 * - number + 'px'
 *
 * @param {string} v
 * @return {function} a function to return
 * - value: number|null
 * - reason(k, v, result)
 */
var LENGTH_VALIDATOR = function LENGTH_VALIDATOR(v) {
  v = (v || '').toString()
  var match = v.match(LENGTH_REGEXP)

  if (match) {
    var unit = match[1]
    if (!unit) {
      return {value: parseFloat(v)}
    }
    else if (SUPPORT_CSS_UNIT.indexOf(unit) > -1) {
      return {value: v}
    }
    else {
      return {
        value: parseFloat(v),
        reason: function reason(k, v, result) {
          return 'NOTE: unit `' + unit + '` is not supported and property value `' + v + '` is autofixed to `' + result + '`'
        }
      }
    }
  }

  return {
    value: null,
    reason: function reason(k, v, result) {
      return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only number and pixel values are supported)'
    }
  }
}

/**
 * the values below is valid
 * - number {1,4}
 * - number + 'px' {1,4}
 *
 * @param {string} v
 * @return {function} a function to return
 * - value: number|null
 * - reason(k, v, result)
 */
var SHORTHAND_LENGTH_VALIDATOR = function SHORTHAND_LENGTH_VALIDATOR(v) {
  v = (v || '').toString()
  var value = []
  var reason = []
  var results = v.split(/\s+/).map(LENGTH_VALIDATOR)
  for (var i = 0; i < results.length; ++i) {
    var res = results[i]
    if (!res.value) {
      value = null
      reason = res.reason
      break
    }
    value.push(res.value)
    reason.push(res.reason)
  }

  if (!value) {
    return {
      value: value,
      reason: reason
    }
  } else {
    return {
      value: value.join(' '),
      reason: function (k, v, result) {
        return reason.map(function (res) {
          if (typeof res === 'function') {
            return res(k, v, result)
          }
        }).join('\n')
      }
    }
  }
}

/**
 * the values below is valid
 * - hex color value (#xxxxxx or #xxx)
 * - basic and extended color keywords in CSS spec
 *
 * @param {string} v
 * @return {function} a function to return
 * - value: string|null
 * - reason(k, v, result)
 */
var COLOR_VALIDATOR = function COLOR_VALIDATOR(v) {
  v = (v || '').toString()

  if (v.match(/^#[0-9a-fA-F]{6}$/)) {
    return {value: v}
  }

  if (v.match(/^#[0-9a-fA-F]{3}$/)) {
    return {
      value: '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3],
      reason: function reason(k, v, result) {
        return 'NOTE: property value `' + v + '` is autofixed to `' + result + '`'
      }
    }
  }

  if (EXTENDED_COLOR_KEYWORDS[v]) {
    return {
      value: EXTENDED_COLOR_KEYWORDS[v],
      reason: function reason(k, v, result) {
        return 'NOTE: property value `' + v + '` is autofixed to `' + result + '`'
      }
    }
  }

  var arrColor, r, g, b, a
  var RGB_REGEXP = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/gi
  var RGBA_REGEXP = /^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d*\.?\d+)\s*\)$/gi
  if (arrColor = RGB_REGEXP.exec(v)) {
    r = parseInt(arrColor[1])
    g = parseInt(arrColor[2])
    b = parseInt(arrColor[3])
    if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
      return {value: 'rgb(' + [r, g, b].join(',') + ')'}
    }
  }
  if (arrColor = RGBA_REGEXP.exec(v)) {
    r = parseInt(arrColor[1])
    g = parseInt(arrColor[2])
    b = parseInt(arrColor[3])
    a = parseFloat(arrColor[4])
    if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 1) {
      return {value: 'rgba(' + [r, g, b, a].join(',') + ')'}
    }
  }
  if (v === 'transparent') {
    return {value: 'rgba(0,0,0,0)'}
  }

  return {
    value: null,
    reason: function reason(k, v, result) {
      return 'ERROR: property value `' + v + '` is not valid for `' + util.camelCaseToHyphened(k) + '`'
    }
  }
}

/**
 * only integer or float value is valid
 *
 * @param {string} v
 * @return {function} a function to return
 * - value: number|null
 * - reason(k, v, result)
 */
var NUMBER_VALIDATOR = function NUMBER_VALIDATOR(v) {
  v = (v || '').toString()
  var match = v.match(LENGTH_REGEXP)

  if (match && !match[1]) {
    return {value: parseFloat(v)}
  }

  return {
    value: null,
    reason: function reason(k, v, result) {
      return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only number is supported)'
    }
  }
}

/**
 * only integer value is valid
 *
 * @param {string} v
 * @return {function} a function to return
 * - value: number|null
 * - reason(k, v, result)
 */
var INTEGER_VALIDATOR = function INTEGER_VALIDATOR(v) {
  v = (v || '').toString()

  if (v.match(/^[-+]?\d+$/)) {
    return {value: parseInt(v, 10)}
  }

  return {
    value: null,
    reason: function reason(k, v, result) {
      return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only integer is supported)'
    }
  }
}

/**
 * transition-property: only css property is valid
 *
 * @param {string} v
 * @return {function} a function to return
 * - value: string|null
 * - reason(k, v, result)
 */
var TRANSITION_PROPERTY_VALIDATOR = function TRANSITION_PROPERTY_VALIDATOR(v) {
  v = (v || '').toString()
  v = v.split(/\s*,\s*/).map(util.hyphenedToCamelCase).join(',')

  if (v.split(/\s*,\s*/).every(p => !!validatorMap[p])) {
    return {value: v}
  }

  return {
    value: null,
    reason: function reason(k, v, result) {
      return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only css property is valid)'
    }
  }
}

/**
 * transition-duration & transition-delay: only number of seconds or milliseconds is valid
 *
 * @param {string} v
 * @return {function} a function to return
 * - value: number|null
 * - reason(k, v, result)
 */
var TRANSITION_INTERVAL_VALIDATOR = function TRANSITION_INTERVAL_VALIDATOR(v) {
  v = (v || 0).toString()
  var match, num, ret

  if (match = v.match(/^\d*\.?\d+(ms|s)?$/)) {
    num = parseFloat(match[0])
    if (!match[1]) {
      ret = {value: parseInt(num)}
    }
    else {
      if (match[1] === 's') {
        num *= 1000
      }
      ret = {
        value: parseInt(num),
        reason: function reason(k, v, result) {
          return 'NOTE: property value `' + v + '` is autofixed to `' + result + '`'
        }
      }
    }
    return ret
  }

  return {
    value: null,
    reason: function reason(k, v, result) {
      return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only number of seconds and milliseconds is valid)'
    }
  }
}

/**
 * transition-timing-function: only linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n) is valid
 *
 * @param {string} v
 * @return {function} a function to return
 * - value: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n)|null
 * - reason(k, v, result)
 */
var TRANSITION_TIMING_FUNCTION_VALIDATOR = function TRANSITION_TIMING_FUNCTION_VALIDATOR(v) {
  v = (v || '').toString()

  if (v.match(/^linear|ease|ease-in|ease-out|ease-in-out$/)) {
    return {value: v}
  }

  var match, ret
  var NUM_REGEXP = /^[-]?\d*\.?\d+$/
  if (match = v.match(/^cubic-bezier\(\s*(.*)\s*,\s*(.*)\s*,\s*(.*)\s*,\s*(.*)\s*\)$/)) {
    /* istanbul ignore else */
    if (match[1].match(NUM_REGEXP) && match[2].match(NUM_REGEXP) && match[3].match(NUM_REGEXP) && match[4].match(NUM_REGEXP)) {
      ret = [parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3]), parseFloat(match[4])].join(',')
      return {value: 'cubic-bezier(' + ret + ')'}
    }
  }

  return {
    value: null,
    reason: function reason(k, v, result) {
      return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (supported values are: `linear`|`ease`|`ease-in`|`ease-out`|`ease-in-out`|`cubic-bezier(n,n,n,n)`)'
    }
  }
}

var TRANSFORM_VALIDATOR = function TRANSFORM_VALIDATOR (v) {
  // TODO
  return { value: v }
}

/**
 * generate a function to check whether a value is in `list`
 * - first value: default, could be removed
 * - not in `list`: error
 *
 * @param  {Array} list
 * @return {function} a function(v) which returns a function to return
 * - value: string|null
 * - reason(k, v, result)
 */
function genEnumValidator(list) {
  return function ENUM_VALIDATOR(v) {
    var index = list.indexOf(v)
    if (index > 0) {
      return {value: v}
    }
    if (index === 0) {
      return {
        value: v,
        reason: function reason(k, v, result) {
          return 'NOTE: property value `' + v + '` is the DEFAULT value for `' + util.camelCaseToHyphened(k) + '` (could be removed)'
        }
      }
    }
    else {
      return {
        value: null,
        reason: function reason(k, v, result) {
          return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (supported values are: `' + list.join('`|`') + '`)'
        }
      }
    }
  }
}

function FLEW_WRAP_VALIDATOR (v) {
  var list = ['nowrap', 'wrap', 'wrap-reverse']
  var index = list.indexOf(v)
  if (index > 0) {
    return {
      value: v,
      reason: function reason(k, v, result) {
        return 'NOTE: the ' + util.camelCaseToHyphened(k) + ' property may have compatibility problem on native'
      }
    }
  }
  if (index === 0) {
    return {
      value: v,
      reason: function reason(k, v, result) {
        return 'NOTE: property value `' + v + '` is the DEFAULT value for `' + util.camelCaseToHyphened(k) + '` (could be removed)'
      }
    }
  }
  else {
    return {
      value: null,
      reason: function reason(k, v, result) {
        return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (supported values are: `' + list.join('`|`') + '`)'
      }
    }
  }
}

var PROP_NAME_GROUPS = {
  boxModel: {
    width: LENGTH_VALIDATOR,
    height: LENGTH_VALIDATOR,
    overflow: genEnumValidator(['hidden']),
    padding: SHORTHAND_LENGTH_VALIDATOR,
    paddingLeft: LENGTH_VALIDATOR,
    paddingRight: LENGTH_VALIDATOR,
    paddingTop: LENGTH_VALIDATOR,
    paddingBottom: LENGTH_VALIDATOR,
    margin: SHORTHAND_LENGTH_VALIDATOR,
    marginLeft: LENGTH_VALIDATOR,
    marginRight: LENGTH_VALIDATOR,
    marginTop: LENGTH_VALIDATOR,
    marginBottom: LENGTH_VALIDATOR,
    borderWidth: LENGTH_VALIDATOR,
    borderLeftWidth: LENGTH_VALIDATOR,
    borderTopWidth: LENGTH_VALIDATOR,
    borderRightWidth: LENGTH_VALIDATOR,
    borderBottomWidth: LENGTH_VALIDATOR,
    borderColor: COLOR_VALIDATOR,
    borderLeftColor: COLOR_VALIDATOR,
    borderTopColor: COLOR_VALIDATOR,
    borderRightColor: COLOR_VALIDATOR,
    borderBottomColor: COLOR_VALIDATOR,
    borderStyle: genEnumValidator(['dotted', 'dashed', 'solid']),
    borderTopStyle: genEnumValidator(['dotted', 'dashed', 'solid']),
    borderRightStyle: genEnumValidator(['dotted', 'dashed', 'solid']),
    borderBottomStyle: genEnumValidator(['dotted', 'dashed', 'solid']),
    borderLeftStyle: genEnumValidator(['dotted', 'dashed', 'solid']),
    borderRadius: LENGTH_VALIDATOR,
    borderBottomLeftRadius: LENGTH_VALIDATOR,
    borderBottomRightRadius: LENGTH_VALIDATOR,
    borderTopLeftRadius: LENGTH_VALIDATOR,
    borderTopRightRadius: LENGTH_VALIDATOR
  },
  flexbox: {
    flex: NUMBER_VALIDATOR,
    flexWrap: FLEW_WRAP_VALIDATOR,
    flexDirection: genEnumValidator(['column', 'row', 'column-reverse', 'row-reverse']),
    justifyContent: genEnumValidator(['flex-start', 'flex-end', 'center', 'space-between','space-around']),
    alignItems: genEnumValidator(['stretch', 'flex-start', 'flex-end', 'center'])
  },
  position: {
    position: genEnumValidator(['relative', 'absolute', 'sticky', 'fixed']),
    top: LENGTH_VALIDATOR,
    bottom: LENGTH_VALIDATOR,
    left: LENGTH_VALIDATOR,
    right: LENGTH_VALIDATOR,
    zIndex: INTEGER_VALIDATOR
  },
  common: {
    opacity: NUMBER_VALIDATOR,
    boxShadow: ANYTHING_VALIDATOR,
    backgroundColor: COLOR_VALIDATOR,
    backgroundImage: ANYTHING_VALIDATOR
  },
  text: {
    lines: INTEGER_VALIDATOR,
    color: COLOR_VALIDATOR,
    fontSize: LENGTH_VALIDATOR,
    fontStyle: genEnumValidator(['normal', 'italic']),
    fontFamily: ANYTHING_VALIDATOR,
    fontWeight: genEnumValidator(['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']),
    textDecoration: genEnumValidator(['none', 'underline', 'line-through']),
    textAlign: genEnumValidator(['left', 'center', 'right']),
    textOverflow: genEnumValidator(['clip', 'ellipsis', 'unset', 'fade']),
    lineHeight: LENGTH_VALIDATOR
  },
  transition: {
    transitionProperty: TRANSITION_PROPERTY_VALIDATOR,
    transitionDuration: TRANSITION_INTERVAL_VALIDATOR,
    transitionDelay: TRANSITION_INTERVAL_VALIDATOR,
    transitionTimingFunction: TRANSITION_TIMING_FUNCTION_VALIDATOR
  },
  transform: {
    transform: TRANSFORM_VALIDATOR,
    transformOrigin: TRANSFORM_VALIDATOR,// fixed by xxxxxx
  },
  customized: {
    itemSize: LENGTH_VALIDATOR,
    itemColor: COLOR_VALIDATOR,
    itemSelectedColor: COLOR_VALIDATOR,
    textColor: COLOR_VALIDATOR,
    timeColor: COLOR_VALIDATOR,
    textHighlightColor: COLOR_VALIDATOR
  }
}

var SUGGESTED_PROP_NAME_GROUP = {
  background: 'backgroundColor'
}

var validatorMap = {}

/**
 * flatten `PROP_NAME_GROUPS` to `validatorMap`
 */
function genValidatorMap() {
  var groupName, group, name
  for (groupName in PROP_NAME_GROUPS) {
    group = PROP_NAME_GROUPS[groupName]
    for (name in group) {
      validatorMap[name] = group[name]
    }
  }
}

genValidatorMap()

/**
 * validate a CSS name/value pair
 *
 * @param  {string} name   camel cased
 * @param  {string} value
 * @return {object}
 * - value:string or null
 * - log:{reason:string} or undefined
 */
function validate(name, value) {
  var result, log
  var validator = validatorMap[name]

  if (typeof validator === 'function') {
    if (typeof value !== 'function') {
      result = validator(value)
    }
    /* istanbul ignore else */
    else {
      result = {value: value}
    }
    if (result.reason) {
      log = {reason: result.reason(name, value, result.value)}
    }
  }
  else {
    // ensure number type, no `px`
    /* istanbul ignore else */
    if (typeof value !== 'function') {
      var match = value.match(LENGTH_REGEXP)
      if (match && (!match[1] || SUPPORT_CSS_UNIT.indexOf(match[1]) === -1)) {
        value = parseFloat(value)
      }
    }
    result = {value: value}
    var suggestedName = SUGGESTED_PROP_NAME_GROUP[name]
    var suggested = suggestedName ? ', suggest `' + util.camelCaseToHyphened(suggestedName) + '`' : ''
    log = {reason: 'WARNING: `' + util.camelCaseToHyphened(name) + '` is not a standard property name (may not be supported)' + suggested}
  }
  return {
    value: result.value,
    log: log
  }
}

module.exports = {
  BASIC_COLOR_KEYWORDS: BASIC_COLOR_KEYWORDS,
  EXTENDED_COLOR_KEYWORDS: EXTENDED_COLOR_KEYWORDS,

  LENGTH_VALIDATOR: LENGTH_VALIDATOR,
  COLOR_VALIDATOR: COLOR_VALIDATOR,
  NUMBER_VALIDATOR: NUMBER_VALIDATOR,
  INTEGER_VALIDATOR: INTEGER_VALIDATOR,
  genEnumValidator: genEnumValidator,

  TRANSITION_PROPERTY_VALIDATOR: TRANSITION_PROPERTY_VALIDATOR,
  TRANSITION_DURATION_VALIDATOR: TRANSITION_INTERVAL_VALIDATOR,
  TRANSITION_DELAY_VALIDATOR: TRANSITION_INTERVAL_VALIDATOR,
  TRANSITION_TIMING_FUNCTION_VALIDATOR: TRANSITION_TIMING_FUNCTION_VALIDATOR,

  PROP_NAME_GROUPS: PROP_NAME_GROUPS,

  map: validatorMap,
  validate: validate
}
