import Ship from '../shipyard/Ship';
import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils';
import { Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import { Modules } from 'coriolis-data/dist';
import { Modifications } from 'coriolis-data/dist';
import { getBlueprint, setQualityCB } from './BlueprintFunctions';

/**
 * Obtain a module given its FD Name
 * @param {string} fdname the FD Name of the module
 * @return {Module} the module
 */
function _moduleFromFdName(fdname) {
  if (!fdname) return null;
  fdname = fdname.toLowerCase();
  // Check standard modules
  for (const grp in Modules.standard) {
    if (Modules.standard.hasOwnProperty(grp)) {
      for (const i in Modules.standard[grp]) {
        if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) {
          // Found it
          return new Module({ template: Modules.standard[grp][i] });
        }
      }
    }
  }

  // Check hardpoint modules
  for (const grp in Modules.hardpoints) {
    if (Modules.hardpoints.hasOwnProperty(grp)) {
      for (const i in Modules.hardpoints[grp]) {
        if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) {
          // Found it
          return new Module({ template: Modules.hardpoints[grp][i] });
        }
      }
    }
  }

  // Check internal modules
  for (const grp in Modules.internal) {
    if (Modules.internal.hasOwnProperty(grp)) {
      for (const i in Modules.internal[grp]) {
        if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) {
          // Found it
          return new Module({ template: Modules.internal[grp][i] });
        }
      }
    }
  }

  // Not found
  return null;
}

/**
 * Build a ship from the journal Loadout event JSON
 * @param {object} json the Loadout event JSON
 * @return {Ship} the built ship
 */
export function shipFromLoadoutJSON(json) {
// Start off building a basic ship
  const shipModel = shipModelFromJson(json);
  if (!shipModel) {
    throw 'No such ship found: "' + json.Ship + '"';
  }
  const shipTemplate = Ships[shipModel];

  let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
  ship.buildWith(null);
  // Initial Ship building, don't do engineering yet.
  let modsToAdd = [];

  for (const module of json.Modules) {
    switch (module.Slot.toLowerCase()) {
      // Cargo Hatch.
      case 'cargohatch':
        ship.cargoHatch.enabled = module.On;
        ship.cargoHatch.priority = module.Priority;
        break;
      // Add the bulkheads
      case 'armour':
        if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
          ship.useBulkhead(0, true);
        } else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
          ship.useBulkhead(1, true);
        } else if (module.Item.toLowerCase().endsWith('_armour_grade3')) {
          ship.useBulkhead(2, true);
        } else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) {
          ship.useBulkhead(3, true);
        } else if (module.Item.toLowerCase().endsWith('_armour_reactive')) {
          ship.useBulkhead(4, true);
        } else {
          throw 'Unknown bulkheads "' + module.Item + '"';
        }
        ship.bulkheads.enabled = true;
        if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
        break;
      case 'powerplant':
        const powerplant = _moduleFromFdName(module.Item);
        ship.use(ship.standard[0], powerplant, true);
        ship.standard[0].enabled = module.On;
        ship.standard[0].priority = module.Priority;
        if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
        break;
      case 'mainengines':
        const thrusters = _moduleFromFdName(module.Item);
        ship.use(ship.standard[1], thrusters, true);
        ship.standard[1].enabled = module.On;
        ship.standard[1].priority = module.Priority;
        if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
        break;
      case 'frameshiftdrive':
        const frameshiftdrive = _moduleFromFdName(module.Item);
        ship.use(ship.standard[2], frameshiftdrive, true);
        ship.standard[2].enabled = module.On;
        ship.standard[2].priority = module.Priority;
        if (module.Engineering)  _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
        break;
      case 'lifesupport':
        const lifesupport = _moduleFromFdName(module.Item);
        ship.use(ship.standard[3], lifesupport, true);
        ship.standard[3].enabled = module.On === true;
        ship.standard[3].priority = module.Priority;
        if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
        break;
      case 'powerdistributor':
        const powerdistributor = _moduleFromFdName(module.Item);
        ship.use(ship.standard[4], powerdistributor, true);
        ship.standard[4].enabled = module.On;
        ship.standard[4].priority = module.Priority;
        if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
        break;
      case 'radar':
        const sensors = _moduleFromFdName(module.Item);
        ship.use(ship.standard[5], sensors, true);
        ship.standard[5].enabled = module.On;
        ship.standard[5].priority = module.Priority;
        if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
        break;
      case 'fueltank':
        const fueltank = _moduleFromFdName(module.Item);
        ship.use(ship.standard[6], fueltank, true);
        ship.standard[6].enabled = true;
        ship.standard[6].priority = 0;
        break;
      default:
    }
    if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
      // Add hardpoints
      let hardpoint;
      let hardpointClassNum = -1;
      let hardpointSlotNum = -1;
      let hardpointArrayNum = 0;
      for (let i in shipTemplate.slots.hardpoints) {
        if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
          // Another slot of the same class
          hardpointSlotNum++;
        } else {
          // The first slot of a new class
          hardpointClassNum = shipTemplate.slots.hardpoints[i];
          hardpointSlotNum = 1;
        }

        // Now that we know what we're looking for, find it
        const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
        const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
        if (!hardpointSlot) {
          // This can happen with old imports that don't contain new hardpoints
        } else {
          hardpoint = _moduleFromFdName(hardpointSlot.Item);
          ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
          ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
          ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
          modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
        }
        hardpointArrayNum++;
      }
    }
  }

  let internalSlotNum = 0;
  let militarySlotNum = 1;
  for (let i in shipTemplate.slots.internal) {
    if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
      continue;
    }
    const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;

    // The internal slot might be a standard or a military slot.  Military slots have a different naming system
    let internalSlot = null;
    if (isMilitary) {
      const internalName = 'Military0' + militarySlotNum;
      internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
      militarySlotNum++;
    } else {
      // Slot numbers are not contiguous so handle skips.
      for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) {
        // Slot sizes have no relationship to the actual size, either, so check all possibilities
        for (let slotsize = 0; slotsize < 9; slotsize++) {
          const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
          if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
            internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
            break;
          }
        }
      }
    }

    if (!internalSlot) {
      // This can happen with old imports that don't contain new slots
    } else {
      const internalJson = internalSlot;
      const internal = _moduleFromFdName(internalJson.Item);
      ship.use(ship.internal[i], internal, true);
      ship.internal[i].enabled = internalJson.On === true;
      ship.internal[i].priority = internalJson.Priority;
      modsToAdd.push({ coriolisMod: internal, json: internalSlot });
    }
  }

  for (const i of modsToAdd) {
    if (i.json.Engineering) {
      _addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.Quality, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
    }
  }
  // We don't have any information on it so guess it's priority 5 and disabled
  if (!ship.cargoHatch) {
    ship.cargoHatch.enabled = false;
    ship.cargoHatch.priority = 4;
  }

  // Now update the ship's codes before returning it
  return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
}

/**
 * Add the modifications for a module
 * @param {Module} module the module
 * @param {Object} modifiers the modifiers
 * @param {float} quality quality of the modifiers 0 to 1
 * @param {Object} blueprint the blueprint of the modification
 * @param {Object} grade the grade of the modification
 * @param {Object} specialModifications special modification
 */
function _addModifications(module, modifiers, quality, blueprint, grade, specialModifications) {
  if (!modifiers && !quality) return;
  let special;
  if (specialModifications) {
    if (specialModifications == 'special_plasma_slug') {
      if (module.symbol.match(/PlasmaAccelerator/i)) {
        specialModifications = 'special_plasma_slug_pa';
      } else {
        specialModifications = 'special_plasma_slug_cooled';
      }
    }
    special = Modifications.specials[specialModifications];
  }
  // Add the blueprint definition, grade and special
  if (blueprint) {
    module.blueprint = getBlueprint(blueprint, module);
    if (grade) {
      module.blueprint.grade = Number(grade);
    }
    if (special) {
      module.blueprint.special = special;
    }
  }
  if (modifiers) {
    for (const i in modifiers) {
      // Some special modifications
      // Look up the modifiers to find what we need to do
      const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '') === val.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, ''));
      const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)];
      // TODO: Figure out how to scale this value.
      if (!!modifiers[i].LessIsGood) {

      }
      let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100;
      if (value === Infinity) {
        value = modifiers[i].Value * 100;
      }
      if (modifiers[i].Label.search('DamageFalloffRange') >= 0) {
        value = (modifiers[i].Value / module.range - 1) * 100;
      }
      if (modifiers[i].Label.search('Resistance') >= 0) {
        value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
      }
      if (modifiers[i].Label.search('ShieldMultiplier') >= 0 || modifiers[i].Label.search('DefenceModifierHealthMultiplier') >= 0) {
        value = ((100 + modifiers[i].Value) / (100 + modifiers[i].OriginalValue) * 100 - 100) * 100;
      }

      // Carry out the required changes
      for (const action in modifierActions) {
        if (isNaN(modifierActions[action])) {
          module.setModValue(action, modifierActions[action]);
        } else {
          module.setModValue(action, value, true);
        }
      }
    }
  } else if (quality) {
    setQualityCB(module.blueprint, quality, (featureName, value) => module.setModValue(featureName, value, false));
  }
}
