const ObjectId = require('bson').ObjectID;
const clone = require('clone');
const tv4 = require('tv4');

const AcuteToxicity = require('./acuteToxicity');
const Dot = require('./dot');
const FamilyBand = require('./family-band');
const Flag = require('./flag');
const Ghs = require('./ghs');
const HealthSymptoms = require('./health-symptoms');
const Nfpa = require('./nfpa');
const Storage = require('./storage');
const SubstanceComponent = require('./substance-component');
const SubstanceIdentifier = require('./substance-identifier');
const Substance = require('./substance');
const InventoryReference = require('./inventory-reference');
const FileReference = require('../procedure/file-reference');

class Family {
  constructor(opt) {
    this._id = null;
    this.hash = null;
    this.identifiers = new SubstanceIdentifier();

    this.appearance = null;
    this.attachments = [];
    this.bands = [];
    this.boilingPoint = {
      min: null,
      max: null,
      unit: null,
    };
    this.components = [];
    this.density = {
      value: null,
      unit: null,
    };
    this.description = null;
    this.dot = new Dot();
    this.firstAid = new HealthSymptoms();
    this.flags = [];
    this.flashPoint = { value: null, unit: null };
    this.form = null;
    this.formAtNtp = null;
    this.formNormalize = null;
    this.formula = null;
    this.ghs = [];
    this.inventory = null;
    this.healthSymptoms = new HealthSymptoms();
    this.inChi = null;
    this.inChiKey = null;
    this.isDataVerified = false;
    this.isFamilyVerified = false;
    this.iupacName = null;
    this.meltingPoint = {
      min: null,
      max: null,
      unit: null,
    };
    this.molecularWeight = {
      value: null,
      unit: null,
    };
    this.name = null;
    this.nfpa = new Nfpa();
    this.pH = null;
    this.pictograms = [];
    this.scope = null;
    this.shelfLife = null;
    this.smiles = null;
    this.storage = new Storage();
    this.synonyms = [];
    this.substances = [];

    this.sds = {
      activeBurnTimeBenchScale: { value: null, unit: null },
      activeBurnTimeIntermediateScale: { value: null, unit: null },
      acuteToxicity: [],
      composition: null,
      criticalPoint: { temperature: { value: null, unit: null }, pressure: { value: null, unit: null } },
      dangerousGoodsDivision: [],
      decompositionInformation: null,
      diameterMetricParticle: { value: null, unit: null },
      evolvesToxicGas: false,
      flammabilityLimit: { lower: null, upper: null },
      heatOfMixing: { value: null, unit: null },
      ignitionPoint: { min: null, max: null, unit: null },
      instantaneousPowerDensity: { value: null, unit: null },
      maximumMassLossRate: { value: null, unit: null },
      particleDensity: { value: null, unit: null },
      particleSize: { min: null, max: null, unit: null },
      peakConvectionHeatRelease: { value: null, unit: null },
      rateOfBurn: { value: null, unit: null },
      rateOfEvolutionOfFlammableGas: {
        oneMinute: { value: null, unit: null },
        oneHour: { value: null, unit: null },
      },
      skinCorrosion: null, // 4hr rabbit skin corrosion test OECD Guideline 40
      vaporPressure: { temperature: { value: null, unit: null }, pressure: { value: null, unit: null } },
      vaporDensity: null,
    };

    if (opt) {
      this.fillObject(clone(opt));
    }
  }

  fillObject(opt) {
    Object.keys(this).forEach((prop) => {
      if (this[prop]) {
        Object.assign(this[prop], opt[prop]);
      } else {
        this[prop] = opt[prop] || null;
      }
    });

    this._id = new ObjectId(opt._id);
    this.sds.acuteToxicity =
      opt.sds && Array.isArray(opt.sds.acuteToxicity)
        ? opt.sds.acuteToxicity.map((item) => new AcuteToxicity(item))
        : [];
    this.sds.dangerousGoodsDivision = opt.sds && opt.sds.dangerousGoodsDivision ? opt.sds.dangerousGoodsDivision : [];
    this.attachments =
      opt.attachments && opt.attachments.length ? opt.attachments.map((item) => new FileReference(item)) : [];
    this.bands = Array.isArray(opt.bands) ? opt.bands.map((band) => new FamilyBand(band)) : [];
    this.components = Array.isArray(opt.components) ? opt.components.map((item) => new SubstanceComponent(item)) : [];
    if (!this.density.value && this.formNormalize === 'GAS' && this.molecularWeight.value) {
      // P = 1 atm
      // T = 293.15 K
      // R = 8.20578 X 10-2 L atm K-1 mol-1
      this.density = {
        value: this.molecularWeight.value / (0.0825078 * 293.15 * 1000), // d = PM/RT * 1L / 1000mL
        unit: 'g/mL',
      };
    }
    this.dot = new Dot(opt.dot);
    this.firstAid = new HealthSymptoms(opt.firstAid);
    this.flags = Array.isArray(opt.flags) ? opt.flags.map((flag) => new Flag(flag)) : [];
    this.ghs = Array.isArray(opt.ghs) ? opt.ghs.map((item) => new Ghs(item)) : [];
    this.inventory = opt.inventory ? new InventoryReference(opt.inventory) : null;
    this.hash = this.hash || this._id.toString();
    this.healthSymptoms = new HealthSymptoms(opt.healthSymptoms);
    this.identifiers = new SubstanceIdentifier(opt.identifiers);
    this.isDataVerified = opt.isDataVerified || false;
    this.isFamilyVerified = opt.isFamilyVerified || false;
    this.nfpa = new Nfpa(opt.nfpa);
    this.pH = opt.pH ? parseFloat(opt.pH, 10) : null;
    this.storage = new Storage(opt.storage);
    this.substances = Array.isArray(opt.substances) ? opt.substances.map((item) => new Substance(item)) : [];

    if (!this.formAtNtp && this.formNormalize) {
      this.formAtNtp = this.formNormalize;
    }
  }

  validate() {
    return tv4.validateMultiple(this, Family.schema());
  }

  static schema() {
    const family = new Family();
    const requiredFamily = Object.keys(family);
    const requiredSds = Object.keys(family.sds);

    const minMaxSchema = {
      type: 'object',
      properties: {
        min: { type: ['null', 'number'] },
        max: { type: ['null', 'number'] },
        unit: { type: ['null', 'string'] },
      },
      required: ['min', 'max', 'unit'],
    };

    const valueUnitSchema = {
      type: 'object',
      properties: {
        value: { type: ['number', 'null'] },
        unit: { type: ['string', 'null'] },
      },
      required: ['value', 'unit'],
    };

    return {
      $schema: 'http://json-schema.org/draft-04/schema#',
      id: 'family.schema.json',
      type: 'object',
      properties: {
        _id: { type: 'object' },
        hash: { type: 'string' },
        identifiers: { $ref: 'substance-identifier.schema.json' },
        appearance: { type: ['string', 'null'] },
        attachments: { type: 'array', item: { $ref: 'file-reference.schema.json' } },
        bands: { type: 'array', item: { $ref: 'family-band.schema.json' } },
        boilingPoint: minMaxSchema,
        components: { type: 'array', item: { $ref: 'substance-component.schema.json' } },
        density: valueUnitSchema,
        description: { type: ['string', 'null'] },
        dot: { $ref: 'dot.schema.json' },
        flags: { type: 'array', item: { $ref: 'flag.schema.json' } },
        flashPoint: valueUnitSchema,
        firstAid: { $ref: 'health-symptoms.schema.json' },
        form: { type: ['string', 'null'] },
        formAtNtp: { type: ['string', 'null'] },
        formNormalize: { type: ['string', 'null'] },
        formula: { type: ['string', 'null'] },
        ghs: { type: 'array', item: { $ref: 'ghs.schema.json' } },
        healthSymptoms: { $ref: 'health-symptoms.schema.json' },
        inChi: { type: ['string', 'null'] },
        inChiKey: { type: ['string', 'null'] },
        inventory: { anyOf: [{ type: ['null'] }, { $ref: 'inventory-reference.schema.json' }] },
        isDataVerified: { type: 'boolean' },
        isFamilyVerified: { type: 'boolean' },
        iupacName: { type: ['string', 'null'] },
        meltingPoint: minMaxSchema,
        molecularWeight: valueUnitSchema,
        name: { type: ['string', 'null'] },
        nfpa: { $ref: 'nfpa.schema.json' },
        packaging: { type: ['string', 'null'] },
        pH: { type: ['number', 'null'] },
        pictograms: { type: 'array', item: { type: 'string' } },
        sds: {
          type: 'object',
          properties: {
            activeBurnTimeBenchScale: valueUnitSchema, // 30g bench scale exposure test
            activeBurnTimeIntermediateScale: valueUnitSchema, // intermediate scale exposure test
            acuteToxicity: { type: 'array', item: { $ref: 'acute-toxicity.schema.json' } },
            composition: { type: ['string', 'null'] },
            criticalPoint: {
              type: 'object',
              properties: {
                temperature: valueUnitSchema,
                pressure: valueUnitSchema,
              },
            },
            dangerousGoodsDivision: { type: 'array', item: { type: 'string' } },
            diameterMetricParticle: valueUnitSchema,
            decompositionInformation: { type: ['string', 'null'] },
            evolvesToxicGas: { type: 'boolean' },
            flammabilityLimit: {
              type: 'object',
              properties: {
                lower: { type: ['number', 'null'] },
                upper: { type: ['number', 'null'] },
              },
            },
            heatOfMixing: valueUnitSchema,
            ignitionPoint: minMaxSchema,
            instantaneousPowerDensity: valueUnitSchema,
            maximumMassLossRate: valueUnitSchema,
            particleDensity: valueUnitSchema,
            particleSize: minMaxSchema,
            peakConvectionHeatRelease: valueUnitSchema,
            rateOfBurn: valueUnitSchema,
            rateOfEvolutionOfFlammableGas: {
              type: 'object',
              properties: {
                oneMinute: valueUnitSchema,
                oneHour: valueUnitSchema,
              },
            },
            skinCorrosion: { type: ['string', 'null'] },
            vaporPressure: {
              type: 'object',
              properties: {
                temperature: valueUnitSchema,
                pressure: valueUnitSchema,
              },
            },
            vaporDensity: { type: ['number', 'null'] },
          },
          required: requiredSds,
        },
        scope: { type: ['string', 'null'] },
        shelfLife: { type: ['number', 'null'] },
        smiles: { type: ['string', 'null'] },
        storage: { $ref: 'storage.schema.json' },
        synonyms: { type: 'array', item: { type: 'string' } },
        substances: { type: 'array', item: { $ref: 'substance-reference.schema.json' } },
      },
      required: requiredFamily,
    };
  }
}

tv4.addSchema(AcuteToxicity.schema());
tv4.addSchema(Dot.schema());
tv4.addSchema(FamilyBand.schema());
tv4.addSchema(Flag.schema());
tv4.addSchema(Ghs.schema());
tv4.addSchema(HealthSymptoms.schema());
tv4.addSchema(InventoryReference.schema());
tv4.addSchema(Nfpa.schema());
tv4.addSchema(Storage.schema());
tv4.addSchema(SubstanceComponent.schema());
tv4.addSchema(SubstanceIdentifier.schema());
tv4.addSchema(Substance.schema());
tv4.addSchema(FileReference.schema());

module.exports = Family;
