import * as convert from "./convert";
import { unitsMap } from "./units";
import { repeatingFractions } from "./repeatingFractions";
import { toTasteMap } from "./numbers";

//import * as Natural from 'natural';

//const nounInflector = new Natural.NounInflector();

export interface Ingredient {
  ingredient: string;
  quantity: string | null;
  unit: string | null;
  minQty: string | null;
  maxQty: string | null;
}

export function toTasteRecognize(input: string, language: string) {
  const toTaste = toTasteMap[language];
  const firstLetter = toTaste.match(/\b(\w)/g);
  //componing first two word
  //const word = firstWord.concat(' ').concat(secondWord)

  if (firstLetter) {
    //checking the extended version
    let regEx = new RegExp("\\b" + toTaste + "\\b", "gi");
    if (input.match(regEx)) {
      return [
        (firstLetter.join(".") + ".").toLocaleLowerCase(),
        convert.getFirstMatch(input, regEx),
        true,
      ] as [string, string, boolean];
    }
    const regExString = firstLetter.join("[.]?") + "[.]?";
    regEx = new RegExp("\\b" + regExString + "\\b", "gi");
    //const a = input.toString().split(/[\s-]+/);
    if (input.match(regEx)) {
      return [
        (firstLetter.join(".") + ".").toLocaleLowerCase(),
        convert.getFirstMatch(input, regEx),
        false,
      ] as [string, string, boolean];
    }
  }
  return ["", "", false] as [string, string, boolean];
}

function getUnit(input: string, language: string) {
  // const word = input.concat(' ').concat(secondWord)
  let unit = unitsMap.get(language);
  let units = unit[0];
  let pluralUnits = unit[1];
  let symbolUnits = unit[3];
  let response = [] as string[];
  const [toTaste, match, extFlag] = toTasteRecognize(input, language);
  // If there is already a unit found for the ingredient, don't add toTaste. Fixes t.t. bug from words with two t's
  if (toTaste && !unit) {
    if (extFlag) {
      response = [toTaste, toTaste, match];
    } else {
      response = [toTaste, toTaste, match];
    }
  } else {
    if (units[input] || pluralUnits[input]) {
      response = [input, pluralUnits[input], input];
    }
    for (const unit of Object.keys(units)) {
      for (const shorthand of units[unit]) {
        const regex = new RegExp("(?=\\b" + shorthand + "\\b)", "gi");
        if (input.match(regex)) {
          response = [unit, pluralUnits[unit], shorthand];
        }
      }
    }
    for (const pluralUnit of Object.keys(pluralUnits)) {
      const regex = new RegExp(
        "(?=\\b" + pluralUnits[pluralUnit] + "\\b)",
        "gi"
      );
      if (input.match(regex)) {
        response = [
          pluralUnit,
          pluralUnits[pluralUnit],
          pluralUnits[pluralUnit],
        ];
      }
    }
  }
  let symbol = symbolUnits[response[0]];
  response.splice(2, 0, symbol);

  return response;
}

/* return the proposition if it's used before of the name of
the ingredient */
function getPreposition(input: string, language: string) {
  let prepositionMap = unitsMap.get(language);
  let prepositions = prepositionMap[2];
  for (const preposition of prepositions) {
    let regex = new RegExp("^" + preposition);
    if (convert.getFirstMatch(input, regex)) return preposition;
  }

  return null;
}

export function parse(recipeString: string, language: string) {
  let ingredientLine = recipeString.trim().replace(/^(-)/, "").toLowerCase(); // removes leading and trailing whitespace
  /* restOfIngredient represents rest of ingredient line.
  For example: "1 pinch salt" --> quantity: 1, restOfIngredient: pinch salt */
  let [quantity, restOfIngredient] = convert.findQuantityAndConvertIfUnicode(
    ingredientLine,
    language
  ) as string[];
  quantity = convert.convertFromFraction(quantity);
  /* extraInfo will be any info in parantheses. We'll place it at the end of the ingredient.
  For example: "sugar (or other sweetener)" --> extraInfo: "(or other sweetener)" */
  let extraInfo;
  if (convert.getFirstMatch(restOfIngredient, /\(([^\)]+)\)/)) {
    extraInfo = convert.getFirstMatch(restOfIngredient, /\(([^\)]+)\)/);
    restOfIngredient = restOfIngredient.replace(extraInfo, "").trim();
  }
  // grab unit and turn it into non-plural version, for ex: "Tablespoons" OR "Tsbp." --> "tablespoon"
  let [unit, unitPlural, symbol, originalUnit] = getUnit(
    restOfIngredient,
    language
  ) as string[];
  // remove unit from the ingredient if one was found and trim leading and trailing whitespace
  let regex_originalunit = RegExp("\\b" + originalUnit + "\\b", "gi");
  let regex_unit = RegExp("\\b" + unit + "\\b", "gi");

  let ingredient = !!originalUnit
    ? restOfIngredient.replace(regex_originalunit, "").trim()
    : restOfIngredient.replace(regex_unit, "").trim();
  ingredient = ingredient.split(".").join("").trim();
  let preposition = getPreposition(ingredient.split(" ")[0], language);

  if (preposition) {
    let regex = new RegExp("^" + preposition);
    ingredient = ingredient.replace(regex, "").trim();
  }

  let minQty = quantity; // default to quantity
  let maxQty = quantity; // default to quantity

  // if quantity is non-nil and is a range, for ex: "1-2", we want to get minQty and maxQty
  if (quantity && quantity.includes("-")) {
    [minQty, maxQty] = quantity.split("-");
  }
  if ((!quantity || quantity == "0") && !unit) {
    unit = "q.b.";
    unitPlural = "q.b.";
  }
  return {
    quantity: +quantity,
    unit: !!unit ? unit : null,
    unitPlural: !!unitPlural ? unitPlural : null,
    symbol: !!symbol ? symbol : null,
    ingredient: extraInfo
      ? `${ingredient} ${extraInfo}`
      : ingredient.replace(/( )*\.( )*/g, ""),
    minQty: +minQty,
    maxQty: +maxQty,
  };
}

export function multiLineParse(recipeString: string, language: string) {
  let ingredients = recipeString.split(/,|👉🏻|👉|\r|\n|-|;/g);
  ingredients = ingredients.filter((line) => {
    // Verifica se la riga contiene una qualsiasi delle varianti della parola "ingredienti"
    if (/ingredient[ei]/i.test(line)) {
      return false;
    }
    // Verifica se la riga contiene solo numeri
    if (/^\d+$/.test(line)) {
      return false;
    }
    // Verifica se la riga contiene solo spazi bianchi o è vuota
    if (/^\s*$/.test(line)) {
      return false;
    }
    return true;
  });
  let result = [];
  let i;
  for (var ingredient of ingredients) {
    i = parse(ingredient, language);
    if (i["ingredient"]) {
      result.push(i);
    }
  }
  return result;
}

export function combine(ingredientArray: Ingredient[]) {
  const combinedIngredients = ingredientArray.reduce((acc, ingredient) => {
    const key = ingredient.ingredient + ingredient.unit; // when combining different units, remove this from the key and just use the name
    const existingIngredient = acc[key];

    if (existingIngredient) {
      return Object.assign(acc, {
        [key]: combineTwoIngredients(existingIngredient, ingredient),
      });
    } else {
      return Object.assign(acc, { [key]: ingredient });
    }
  }, {} as { [key: string]: Ingredient });

  return Object.keys(combinedIngredients)
    .reduce((acc, key) => {
      const ingredient = combinedIngredients[key];
      return acc.concat(ingredient);
    }, [] as Ingredient[])
    .sort(compareIngredients);
}

export function prettyPrintingPress(ingredient: Ingredient) {
  let quantity = "";
  let unit = ingredient.unit;
  if (ingredient.quantity) {
    const [whole, remainder] = ingredient.quantity.split(".");
    if (+whole !== 0 && typeof whole !== "undefined") {
      quantity = whole;
    }
    if (+remainder !== 0 && typeof remainder !== "undefined") {
      let fractional;
      if (repeatingFractions[remainder]) {
        fractional = repeatingFractions[remainder];
      } else {
        const fraction = "0." + remainder;
        const len = fraction.length - 2;
        let denominator = Math.pow(10, len);
        let numerator = +fraction * denominator;

        const divisor = gcd(numerator, denominator);

        numerator /= divisor;
        denominator /= divisor;
        fractional = Math.floor(numerator) + "/" + Math.floor(denominator);
      }

      quantity += quantity ? " " + fractional : fractional;
    }
    /* if (((+whole !== 0 && typeof remainder !== 'undefined') || +whole > 1) && unit) {
       unit = nounInflector.pluralize(unit);
     }*/
  } else {
    return ingredient.ingredient;
  }

  return `${quantity}${unit ? " " + unit : ""} ${ingredient.ingredient}`;
}

function gcd(a: number, b: number): number {
  if (b < 0.0000001) {
    return a;
  }

  return gcd(b, Math.floor(a % b));
}

// TODO: Maybe change this to existingIngredients: Ingredient | Ingredient[]
function combineTwoIngredients(
  existingIngredients: Ingredient,
  ingredient: Ingredient
): Ingredient {
  const quantity =
    existingIngredients.quantity && ingredient.quantity
      ? (
          Number(existingIngredients.quantity) + Number(ingredient.quantity)
        ).toString()
      : null;
  const minQty =
    existingIngredients.minQty && ingredient.minQty
      ? (
          Number(existingIngredients.minQty) + Number(ingredient.minQty)
        ).toString()
      : null;
  const maxQty =
    existingIngredients.maxQty && ingredient.maxQty
      ? (
          Number(existingIngredients.maxQty) + Number(ingredient.maxQty)
        ).toString()
      : null;
  return Object.assign({}, existingIngredients, { quantity, minQty, maxQty });
}

function compareIngredients(a: Ingredient, b: Ingredient) {
  if (a.ingredient === b.ingredient) {
    return 0;
  }
  return a.ingredient < b.ingredient ? -1 : 1;
}
