/** Interface for string, compared string and distance beetween */
export interface StringWithDistance {
  string: string;
  compared: string;
  distance: number;
}

const initMatrix = (a: string, b: string): number[][] => {
  let d: number[][] = [];

  for (let i = 0; i <= a.length; i++) {
    d[i] = [];
    d[i][0] = i;
  }
  for (let j = 0; j <= b.length; j++) {
    d[0][j] = j;
  }

  return d;
};

const damerau = (
  i: number,
  j: number,
  a: string,
  b: string,
  d: number[][],
  cost: number,
) => {
  if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
    d[i][j] = Math.min.apply(null, [d[i][j], d[i - 2][j - 2] + cost]);
  }
};

/** Get the Damerau-Levenshtein distance between 2 strings */
export function distance(a: string, b: string) {
  let d: number[][] = initMatrix(a, b);
  for (var i = 1; i <= a.length; i++) {
    let cost: number;
    for (let j = 1; j <= b.length; j++) {
      if (a.charAt(i - 1) === b.charAt(j - 1)) {
        cost = 0;
      } else {
        cost = 1;
      }

      d[i][j] = Math.min.apply(null, [
        d[i - 1][j] + 1,
        d[i][j - 1] + 1,
        d[i - 1][j - 1] + cost,
      ]);

      damerau(i, j, a, b, d, cost);
    }
  }
  return d[a.length][b.length];
}

/** Return an arry of StringWithDistance with the distance from the compared string */
export function distanceList(
  target: string,
  list: Array<string>,
): Array<StringWithDistance> {
  return list.map((string) => {
    return distanceDamerau(target, string);
  });
}

/** Return an object with string, compared string and distance beetween */
export function distanceDamerau(
  string: string,
  compared: string,
): StringWithDistance {
  return {
    string: string,
    compared: compared,
    distance: distance(string, compared),
  };
}

/** Compare distance between 2 words (format like StringWithDistance). */
export function compareDistance(
  a: StringWithDistance,
  b: StringWithDistance,
): number {
  return a.distance > b.distance ? 1 : a.distance < b.distance ? -1 : 0;
}

/**  Get the minimum Damerau-Levenshtein distance between a string and an array of strings */
export function minDistance(
  string: string,
  list: Array<string>,
): number {
  const arrayStrings: Array<StringWithDistance> = distanceList(string, list);
  return arrayStrings.length === 0 ? string.length : arrayStrings.reduce(
    (min, b) => Math.min(min, b.distance),
    arrayStrings[0].distance,
  );
} /** Return an arry of StringWithDistance sorted by min distance */

export function sortByMinDistance(
  list: Array<StringWithDistance>,
): Array<StringWithDistance> {
  return list.concat().sort(compareDistance);
}

/** Return an arry of StringWithDistance sorted by min distance */
export function sortWordByMinDistance(
  target: string,
  list: Array<string>,
): Array<StringWithDistance> {
  const listWithDistance: Array<StringWithDistance> = distanceList(
    target,
    list,
  );
  return sortByMinDistance(listWithDistance);
}

// interface CompareStrings {
//   firstString: string;
//   secondString: string;
//   target: string;
// }
//
// export function compareDistanceBetweenWords(obj: CompareStrings): number {
//   return compareDistance(
//     distanceDamerau(obj.firstString, obj.target),
//     distanceDamerau(obj.secondString, obj.target),
//   );
// }