/*
This module have to be use in back as well front
can be include in project with
  - into a browser : <script src="https://townName.nationName.dns/nationchains/contracts/Checkjson.js"></script>
  - into a node.js : const Checkjson = require( `../nationchains/socialworld/contracts/Checkjson.js`);
*/
// --##

const Checkjson = {};
Checkjson.schema = {};
Checkjson.schema.properties = {};
Checkjson.schema.properties.type = {};
Checkjson.schema.properties.type.string = (str) => typeof str === "string";
Checkjson.schema.properties.type.array = (val)=> Array.isArray(val);
Checkjson.schema.properties.type.number = (n) => typeof n === "number";
Checkjson.schema.properties.type.boolean = (n) => typeof n === "boolean";
Checkjson.schema.properties.type.integer = (n) =>
  n != "" && !isNaN(n) && Math.round(n) == n;
Checkjson.schema.properties.type.float = (n) =>
  n != "" && !isNaN(n) && Math.round(n) != n; //not yet in json schema
Checkjson.schema.properties.minLength = (str, min) =>
  typeof str === "string" && str.length > parseInt(min);
Checkjson.schema.properties.maxLength = (str, max) =>
  typeof str === "string" && str.length < parseInt(max);
Checkjson.schema.properties.multipleOf = (n, val) =>
  typeof n === "number" &&
  typeof val === "number" &&
  parseFloat(n) / parseFloat(val) -
    Math.round(parseFloat(n) / parseFloat(val)) <
    0.0000001;
Checkjson.schema.properties.range = (
  n,
  minimum,
  exclusiveMinimum,
  maximum,
  exclusiveMaximum
) => {
  //console.log(minimum,exclusiveMinimum,maximum, exclusiveMaximum,n)
  if (typeof n !== "number") return false;
  if (minimum && parseFloat(n) < parseFloat(minimum)) return false;
  if (exclusiveMinimum && parseFloat(n) <= parseFloat(exclusiveMinimum))
    return false;
  if (maximum && parseFloat(n) > parseFloat(maximum)) return false;
  if (exclusiveMaximum && parseFloat(n) >= parseFloat(exclusiveMaximum))
    return false;
  return true;
};
Checkjson.schema.properties.pattern = (str, pattern) => {
  try {
    pattern= new RegExp(pattern);
  } catch (e) {
    console.log('err pattern in checkjon',pattern);
    return false;
  }
  return pattern.test(str);
};
Checkjson.schema.properties.enum = (str, enumvalues) =>
  typeof str === "string" && enumvalues.includes(str);
// see format https://json-schema.org/understanding-json-schema/reference/string.html#format
Checkjson.schema.properties.format = {
  "date-time": /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d{1,3}/,
  stringalphaonly:/^[A-Za-z0-9]{3,}$/,
  time: /[0-2]\d:[0-5]\d:[0-5]\d\.\d{1,3}/,
  date: /\d{4}-[01]\d-[0-3]\d/,
  duration: / /,
  email:
    /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
  "idn-email": / /,
  uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
  uri: / /,
  "uri-reference": / /,
  iri: / /,
  hostname: / /,
  "idn-hostname": / /,
  ipv4: /^([0–9]{1,3}.){3}.([0–9]{1,3})$/,
  ipv6: /^((([0–9A-Fa-f]{1,4}:){7}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){6}:[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){5}:([0–9A-Fa-f]{1,4}:)?[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){4}:([0–9A-Fa-f]{1,4}:){0,2}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){3}:([0–9A-Fa-f]{1,4}:){0,3}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){2}:([0–9A-Fa-f]{1,4}:){0,4}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){6}((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|(([0–9A-Fa-f]{1,4}:){0,5}:((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|(::([0–9A-Fa-f]{1,4}:){0,5}((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|([0–9A-Fa-f]{1,4}::([0–9A-Fa-f]{1,4}:){0,5}[0–9A-Fa-f]{1,4})|(::([0–9A-Fa-f]{1,4}:){0,6}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){1,7}:))$/,
  telephonefr: /^0[1-9][0-9]{9}$/,
  telephoneinter: /^\+*(\d{3})*[0-9,\-]{8,}/,
  password:
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/,
  postalcodefr: /(^\d{5}$)|(^\d{5}-\d{4}$)/,
};
Checkjson.schema.properties.default
Checkjson.schema.validation = (schema) => {
  /*validate a schema structure*/
  const res = { status: 200, err: [] };
  if (schema.properties) {
    Object.keys(schema.properties).forEach((p) => {
      const properties = schema.properties;
      if (
        properties[p].type &&
        typeof properties[p].type === "string" &&
        !Checkjson.schema.properties.type[properties[p].type]
      ) {
        res.err.push({
          ref:"Checkjson",
          msg:"schemaerrtypedoesnotexist",
          data: {propertie:p,type:properties[p].type}
        });
      }
      if (
        properties[p].type &&
        typeof properties[p].type === "object" &&
        Array.isArray(properties[p].type)
      ) {
        properties[p].type.forEach((tp) => {
          if (!Checkjson.schema.properties.type[tp])
            res.err.push({
              ref:"Checkjson",
              msg:"schemaerrtypedoesnotexist",
              data: {propertie:p,type:properties[p].type}
            });
        });
      }
      if (
        properties[p].format &&
        !Checkjson.schema.properties.format[properties[p].format]
      ) {
        res.err.push({
          ref:"Checkjson",
          msg:"schemaerrformatdoesnotexist",
          data: {propertie:p,format:properties[p].format}
        });
      }
      if (properties[p].enum && !Array.isArray(properties[p].enum)) {
        res.err.push({
          ref:"Checkjson",
          msg:"schemaerrenumnotarray",
          data: {propertie:p,enum:properties[p].enum}
        });
      }
    });
  }
  // 406 means not acceptable
  if (res.err.length > 0) res.status = 406;
  return res;
};

Checkjson.schema.data = (schema, data, withschemacheck) => {
  /* validate a data set with a schema in a context ctx */
  /*
  console.log('#################')
  console.log(schema);
  console.log('---------')
  console.log(data)
  */
  if (withschemacheck) {
    const validschema = Checkjson.schema.validation(schema);
    if (validschema.status != 200) return validschema;
  }
  const res = { status: 200, err: [] };
  if (schema.properties) {
    const properties = schema.properties;
    Object.keys(properties).forEach((p) => {
      //type is mandatory in a propertie
      if (data[p]) {
        const typlist =
          properties[p].type && typeof properties[p].type === "string"
            ? [properties[p].type]
            : properties[p].type;
        let valid = false;
        typlist.forEach((typ) => {
          // at least one test have to be ok
          if (Checkjson.schema.properties.type[typ](data[p])) valid = true;
        });
        if (!valid)
          res.err.push({
            ref:"Checkjson",
            msg:"dataerrpropertie",
            data: {key:p,value:data[p]}
          });

        if (
          properties[p].minLength &&
          !Checkjson.schema.properties.minLength(data[p], properties[p].minLength)
        ) {
          res.err.push({
            ref:"Checkjson",
            msg:"dataerrpropertie",
            data:{key:p,value:data[p],minLength:properties[p].minLength}
           });
        }
        if (
          properties[p].maxLength &&
          !Checkjson.schema.properties.maxLength(data[p], properties[p].maxLength)
        ) {
          res.err.push({
            ref:"Checkjson",
            msg:"dataerrpropertie",
            data:{key:p,value:data[p],maxLength:properties[p].maxLength}
          });
        }
        if (
          properties[p].multipleOf &&
          !Checkjson.schema.properties.multipleOf(data[p], properties[p].multipleOf)
        ) {
          res.err.push({
            ref:"Checkjson",
            msg:"dataerrpropertie",
            data:{key:p,value:data[p],multipleOf:properties[p].multipleOf}
          });
        }
        if (
          properties[p].minimum ||
          properties[p].maximum ||
          properties[p].exclusiveMinimum ||
          properties[p].exclusiveMaximum
        ) {
          // test range
          if (
            !Checkjson.schema.properties.range(
              data[p],
              properties[p].minimum,
              properties[p].exclusiveMinimum,
              properties[p].maximum,
              properties[p].exclusiveMaximum
            )
          ) {
            res.err.push({
              ref:"Checkjson",
              msg:"dataerrpropertie",
              data:{key:p,value:data[p],minimum:properties[p].minimum,maximum:properties[p].maximum,exclusiveMinimum:properties[p].exclusiveMinimum,exclusiveMaximum:properties[p].exclusiveMaximum}
            });
          }
        }
        if (
          properties[p].enum &&
          !Checkjson.schema.properties.enum(data[p], properties[p].enum)
        ) {
          res.err.push({
            ref:"Checkjson",
            msg:"dataerrpropertie",
            data:{key:p,value:data[p],enumlst:properties[p].enum}
          });
        }
        if (properties[p].format) {
          properties[p].pattern =
            Checkjson.schema.properties.format[properties[p].format];
        }
        if (
          properties[p].pattern &&
          !Checkjson.schema.properties.pattern(data[p], properties[p].pattern)
        ) {
          res.err.push({
            ref:"Checkjson",
            msg:"dataerrpropertie",
            data:{key:p,value:data[p],pattern:properties[p].pattern}
          });
        }
      } else if (schema.required && schema.required.includes(p)) {
        res.err.push({
            ref:"Checkjson",
            msg:"dataerrpropertierequired",
            data:{key:p,required:true}
        });
      }
    });
  }
  if (res.err.length > 0) res.status = 417;
  return res;
};
if (typeof module !== "undefined") module.exports = Checkjson;
