import { pond } from "protobuf-ts/pond";
import GrainDescriber from "./GrainDescriber";

export enum Equation {
  "none",
  "oswin",
  "halsey",
  "henderson",
  "chungPfost"
}

const toERH = (humidity: number): number => {
  return humidity >= 100 ? 0.99999 : humidity / 100;
};

export function ExtractMoisture(
  type: pond.Grain | undefined,
  celsius: number,
  humidity: number
): number {
  if (humidity <= 0) {
    return 0;
  }

  if (
    type === undefined ||
    type === pond.Grain.GRAIN_NONE ||
    type === pond.Grain.GRAIN_INVALID ||
    type === pond.Grain.GRAIN_CUSTOM
  ) {
    return humidity;
  }

  let dry = humidity;
  let ctx = GrainDescriber(type);
  dry = toDryMoisture(ctx.equation, celsius, humidity, ctx.a, ctx.b, ctx.c);
  return dryToWet(dry);
}

export function WaterContent(type: pond.Grain, bushels: number, moistureContent: number): number {
  let grain = GrainDescriber(type);
  return bushels * grain.weightConversionKg * moistureContent;
}

export function MoistureToHumidity(
  type: pond.Grain | undefined,
  celsius: number,
  wetMC: number
): number {
  if (wetMC <= 0) {
    return 0;
  }

  if (type === undefined || type === pond.Grain.GRAIN_NONE || type === pond.Grain.GRAIN_INVALID) {
    return wetMC;
  }

  const dryMC = wetToDry(wetMC);
  let ctx = GrainDescriber(type);
  return dryMoistureToHumidity(ctx.equation, celsius, dryMC, ctx.a, ctx.b, ctx.c);
}

function dryToWet(dryMC: number): number {
  return (100 * dryMC) / (dryMC + 100);
}

function wetToDry(wetMC: number): number {
  return -((100 * wetMC) / (wetMC - 100));
}

function toDryMoisture(
  eq: Equation,
  T: number,
  RH: number,
  a: number,
  b: number,
  c: number
): number {
  const ERH: number = toERH(RH);
  switch (eq) {
    case Equation.chungPfost:
      return chungPfost(T, ERH, a, b, c);
    case Equation.halsey:
      return halsey(T, ERH, a, b, c);
    case Equation.henderson:
      return henderson(T, ERH, a, b, c);
    case Equation.oswin:
      return oswin(T, ERH, a, b, c);
    default:
      return RH;
  }
}

function dryMoistureToHumidity(
  eq: Equation,
  celsius: number,
  dryMC: number,
  a: number,
  b: number,
  c: number
): number {
  let ERH = 0;
  switch (eq) {
    case Equation.chungPfost:
      ERH = chungPfostInverse(celsius, dryMC, a, b, c);
      break;
    case Equation.halsey:
      ERH = halseyInverse(celsius, dryMC, a, b, c);
      break;
    case Equation.henderson:
      ERH = hendersonInverse(celsius, dryMC, a, b, c);
      break;
    case Equation.oswin:
      ERH = oswinInverse(celsius, dryMC, a, b, c);
      break;
    default:
      return dryMC;
  }

  let RH = ERH * 100;
  return RH;
}

function chungPfost(celsius: number, ERH: number, a: number, b: number, c: number): number {
  const T = celsius;
  return Math.log((-Math.log(ERH) * (T + c)) / a) / -b;
}

function halsey(celsius: number, ERH: number, a: number, b: number, c: number): number {
  const T = celsius;
  return Math.pow(-Math.exp(a + b * T) / Math.log(ERH), 1 / c);
}

function henderson(celsius: number, ERH: number, a: number, b: number, c: number): number {
  const T = celsius;
  return Math.pow(Math.log(1 - ERH) / (-a * (T + c)), 1 / b);
}

function oswin(celsius: number, ERH: number, a: number, b: number, c: number): number {
  const T = celsius;
  return (a + b * T) / Math.pow(Math.pow(ERH, -1) - 1, 1 / c);
}

function chungPfostInverse(
  celsius: number,
  dryMC: number,
  a: number,
  b: number,
  c: number
): number {
  const T = celsius;
  return Math.exp(-(a / (T + c)) * Math.exp(-b * dryMC));
}

function halseyInverse(celsius: number, dryMC: number, a: number, b: number, c: number): number {
  const T = celsius;
  return Math.exp(-(Math.exp(a + b * T) / Math.pow(dryMC, c)));
}

function hendersonInverse(celsius: number, dryMC: number, a: number, b: number, c: number): number {
  const T = celsius;
  return 1 - Math.exp(-a * (T + c) * Math.pow(dryMC, b));
}

function oswinInverse(celsius: number, dryMC: number, a: number, b: number, c: number): number {
  const T = celsius;
  return Math.pow(Math.pow((a + b * T) / dryMC, c) + 1, -1);
}
