text: /**
   *
   *    Copyright (c) 2020 Silicon Labs
   *
   *    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.
   */
  
  /**
   * This module provides the APIs for validating inputs to the database, and returning flags indicating if
   * things were successful or not.
   *
   */
  
  const queryZcl = require('../db/query-zcl.js')
  const queryConfig = require('../db/query-config.js')
  const types = require('../util/types.js')
  
  async function validateAttribute(db, endpointTypeId, attributeRef, clusterRef) {
    let endpointAttribute = await queryZcl.selectEndpointTypeAttribute(
      db,
      endpointTypeId,
      attributeRef,
      clusterRef
    )
    let attribute = await queryZcl.selectAttributeById(db, attributeRef)
  
    return validateSpecificAttribute(endpointAttribute, attribute)
  }
  
  async function validateEndpoint(db, endpointId) {
    let endpoint = await queryConfig.selectEndpoint(db, endpointId)
    let currentIssues = validateSpecificEndpoint(endpoint)
    let noDuplicates = await validateNoDuplicateEndpoints(
      db,
      endpoint.endpointId,
      endpoint.sessionRef
    )
    if (!noDuplicates) {
      currentIssues.endpointId.push('Duplicate EndpointIds Exist')
    }
    return currentIssues
  }
  
  async function validateNoDuplicateEndpoints(
    db,
    endpointIdentifier,
    sessionRef
  ) {
    let count = await queryConfig.selectCountOfEndpointsWithGivenEndpointIdentifier(
      db,
      endpointIdentifier,
      sessionRef
    )
    return count.length <= 1
  }
  
  function validateSpecificAttribute(endpointAttribute, attribute) {
    let defaultAttributeIssues = []
    if (!types.isString(attribute.type)) {
      if (types.isFloat(attribute.type)) {
        if (!isValidFloat(endpointAttribute.defaultValue))
          defaultAttributeIssues.push('Invalid Float')
        //Interpreting float values
        if (!checkAttributeBoundsFloat(attribute, endpointAttribute))
          defaultAttributeIssues.push('Out of range')
      } else {
        if (!isValidNumberString(endpointAttribute.defaultValue))
          defaultAttributeIssues.push('Invalid Integer')
        if (!checkAttributeBoundsInteger(attribute, endpointAttribute))
          defaultAttributeIssues.push('Out of range')
      }
    }
    return { defaultValue: defaultAttributeIssues }
  }
  
  function validateSpecificEndpoint(endpoint) {
    let zclEndpointIdIssues = []
    let zclNetworkIdIssues = []
    if (!isValidNumberString(endpoint.endpointId))
      zclEndpointIdIssues.push('EndpointId is invalid number string')
    if (
      extractIntegerValue(endpoint.endpointId) > 0xffff ||
      extractIntegerValue(endpoint.endpointId) < 0
    )
      zclEndpointIdIssues.push('EndpointId is out of valid range')
    if (!isValidNumberString(endpoint.networkId))
      zclNetworkIdIssues.push('NetworkId is invalid number string')
    if (extractIntegerValue(endpoint.endpointId) == 0)
      zclEndpointIdIssues.push('0 is not a valid endpointId')
    return {
      endpointId: zclEndpointIdIssues,
      networkId: zclNetworkIdIssues,
    }
  }
  
  //This applies to both actual numbers as well as octet strings.
  function isValidNumberString(value) {
    //We test to see if the number is valid in hex. Decimals numbers also pass this test
    return /^(0x|0X)?[0-9a-fA-F]+$/.test(value)
  }
  
  function isValidFloat(value) {
    return /^[0-9]*(\.)?[0-9]*$/.test(value)
  }
  
  function extractFloatValue(value) {
    return parseFloat(value)
  }
  
  function extractIntegerValue(value) {
    if (/^[0-9]+$/.test(value)) {
      return parseInt(value)
    } else if (/^[0-9ABCDEF]+$/.test(value)) {
      return parseInt(value, 16)
    } else {
      return parseInt(value, 16)
    }
  }
  
  function getBoundsInteger(attribute) {
    return {
      min: extractIntegerValue(attribute.min),
      max: extractIntegerValue(attribute.max),
    }
  }
  
  function checkAttributeBoundsInteger(attribute, endpointAttribute) {
    let { min, max } = getBoundsInteger(attribute)
    let defaultValue = extractIntegerValue(endpointAttribute.defaultValue)
    return checkBoundsInteger(defaultValue, min, max)
  }
  
  function checkBoundsInteger(defaultValue, min, max) {
    if (Number.isNaN(min)) min = Number.MIN_SAFE_INTEGER
    if (Number.isNaN(max)) max = Number.MAX_SAFE_INTEGER
    return defaultValue >= min && defaultValue <= max
  }
  
  function checkAttributeBoundsFloat(attribute, endpointAttribute) {
    let { min, max } = getBoundsFloat(attribute)
    let defaultValue = extractFloatValue(endpointAttribute.defaultValue)
    return checkBoundsFloat(defaultValue, min, max)
  }
  
  function getBoundsFloat(attribute) {
    return {
      min: extractFloatValue(attribute.min),
      max: extractFloatValue(attribute.max),
    }
  }
  
  function checkBoundsFloat(defaultValue, min, max) {
    if (Number.isNaN(min)) min = Number.MIN_VALUE
    if (Number.isNaN(max)) max = Number.MAX_VALUE
    return defaultValue >= min && defaultValue <= max
  }
  
  // exports
  exports.validateAttribute = validateAttribute
  exports.validateEndpoint = validateEndpoint
  exports.validateNoDuplicateEndpoints = validateNoDuplicateEndpoints
  exports.validateSpecificAttribute = validateSpecificAttribute
  exports.validateSpecificEndpoint = validateSpecificEndpoint
  exports.isValidNumberString = isValidNumberString
  exports.isValidFloat = isValidFloat
  exports.extractFloatValue = extractFloatValue
  exports.extractIntegerValue = extractIntegerValue
  exports.getBoundsInteger = getBoundsInteger
  exports.checkBoundsInteger = checkBoundsInteger
  exports.getBoundsFloat = getBoundsFloat
  exports.checkBoundsFloat = checkBoundsFloat
  