import { toFixed, toFixedScaled } from './valueFormats';
import { DecimalCount } from '../../types';
import { toUtc, toDuration as duration, dateTime } from '@grafana/data';

interface IntervalsInSeconds {
  [interval: string]: number;
}

export enum Interval {
  Year = 'year',
  Month = 'month',
  Week = 'week',
  Day = 'day',
  Hour = 'hour',
  Minute = 'minute',
  Second = 'second',
  Millisecond = 'millisecond',
}

const INTERVALS_IN_SECONDS: IntervalsInSeconds = {
  [Interval.Year]: 31536000,
  [Interval.Month]: 2592000,
  [Interval.Week]: 604800,
  [Interval.Day]: 86400,
  [Interval.Hour]: 3600,
  [Interval.Minute]: 60,
  [Interval.Second]: 1,
  [Interval.Millisecond]: 0.001,
};

export function toNanoSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
  if (size === null) {
    return '';
  }

  if (Math.abs(size) < 1000) {
    return toFixed(size, decimals) + ' ns';
  } else if (Math.abs(size) < 1000000) {
    return toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' µs');
  } else if (Math.abs(size) < 1000000000) {
    return toFixedScaled(size / 1000000, decimals, scaledDecimals, 6, ' ms');
  } else if (Math.abs(size) < 60000000000) {
    return toFixedScaled(size / 1000000000, decimals, scaledDecimals, 9, ' s');
  } else {
    return toFixedScaled(size / 60000000000, decimals, scaledDecimals, 12, ' min');
  }
}

export function toMicroSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
  if (size === null) {
    return '';
  }

  if (Math.abs(size) < 1000) {
    return toFixed(size, decimals) + ' µs';
  } else if (Math.abs(size) < 1000000) {
    return toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' ms');
  } else {
    return toFixedScaled(size / 1000000, decimals, scaledDecimals, 6, ' s');
  }
}

export function toMilliSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
  if (size === null) {
    return '';
  }

  if (Math.abs(size) < 1000) {
    return toFixed(size, decimals) + ' ms';
  } else if (Math.abs(size) < 60000) {
    // Less than 1 min
    return toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' s');
  } else if (Math.abs(size) < 3600000) {
    // Less than 1 hour, divide in minutes
    return toFixedScaled(size / 60000, decimals, scaledDecimals, 5, ' min');
  } else if (Math.abs(size) < 86400000) {
    // Less than one day, divide in hours
    return toFixedScaled(size / 3600000, decimals, scaledDecimals, 7, ' hour');
  } else if (Math.abs(size) < 31536000000) {
    // Less than one year, divide in days
    return toFixedScaled(size / 86400000, decimals, scaledDecimals, 8, ' day');
  }

  return toFixedScaled(size / 31536000000, decimals, scaledDecimals, 10, ' year');
}

export function trySubstract(value1: DecimalCount, value2: DecimalCount): DecimalCount {
  if (value1 !== null && value1 !== undefined && value2 !== null && value2 !== undefined) {
    return value1 - value2;
  }
  return undefined;
}

export function toSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
  if (size === null) {
    return '';
  }

  // Less than 1 µs, divide in ns
  if (Math.abs(size) < 0.000001) {
    return toFixedScaled(size * 1e9, decimals, trySubstract(scaledDecimals, decimals), -9, ' ns');
  }
  // Less than 1 ms, divide in µs
  if (Math.abs(size) < 0.001) {
    return toFixedScaled(size * 1e6, decimals, trySubstract(scaledDecimals, decimals), -6, ' µs');
  }
  // Less than 1 second, divide in ms
  if (Math.abs(size) < 1) {
    return toFixedScaled(size * 1e3, decimals, trySubstract(scaledDecimals, decimals), -3, ' ms');
  }

  if (Math.abs(size) < 60) {
    return toFixed(size, decimals) + ' s';
  } else if (Math.abs(size) < 3600) {
    // Less than 1 hour, divide in minutes
    return toFixedScaled(size / 60, decimals, scaledDecimals, 1, ' min');
  } else if (Math.abs(size) < 86400) {
    // Less than one day, divide in hours
    return toFixedScaled(size / 3600, decimals, scaledDecimals, 4, ' hour');
  } else if (Math.abs(size) < 604800) {
    // Less than one week, divide in days
    return toFixedScaled(size / 86400, decimals, scaledDecimals, 5, ' day');
  } else if (Math.abs(size) < 31536000) {
    // Less than one year, divide in week
    return toFixedScaled(size / 604800, decimals, scaledDecimals, 6, ' week');
  }

  return toFixedScaled(size / 3.15569e7, decimals, scaledDecimals, 7, ' year');
}

export function toMinutes(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
  if (size === null) {
    return '';
  }

  if (Math.abs(size) < 60) {
    return toFixed(size, decimals) + ' min';
  } else if (Math.abs(size) < 1440) {
    return toFixedScaled(size / 60, decimals, scaledDecimals, 2, ' hour');
  } else if (Math.abs(size) < 10080) {
    return toFixedScaled(size / 1440, decimals, scaledDecimals, 3, ' day');
  } else if (Math.abs(size) < 604800) {
    return toFixedScaled(size / 10080, decimals, scaledDecimals, 4, ' week');
  } else {
    return toFixedScaled(size / 5.25948e5, decimals, scaledDecimals, 5, ' year');
  }
}

export function toHours(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
  if (size === null) {
    return '';
  }

  if (Math.abs(size) < 24) {
    return toFixed(size, decimals) + ' hour';
  } else if (Math.abs(size) < 168) {
    return toFixedScaled(size / 24, decimals, scaledDecimals, 2, ' day');
  } else if (Math.abs(size) < 8760) {
    return toFixedScaled(size / 168, decimals, scaledDecimals, 3, ' week');
  } else {
    return toFixedScaled(size / 8760, decimals, scaledDecimals, 4, ' year');
  }
}

export function toDays(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
  if (size === null) {
    return '';
  }

  if (Math.abs(size) < 7) {
    return toFixed(size, decimals) + ' day';
  } else if (Math.abs(size) < 365) {
    return toFixedScaled(size / 7, decimals, scaledDecimals, 2, ' week');
  } else {
    return toFixedScaled(size / 365, decimals, scaledDecimals, 3, ' year');
  }
}

export function toDuration(size: number, decimals: DecimalCount, timeScale: Interval): string {
  if (size === null) {
    return '';
  }

  if (size === 0) {
    return '0 ' + timeScale + 's';
  }

  if (size < 0) {
    return toDuration(-size, decimals, timeScale) + ' ago';
  }

  const units = [
    { long: Interval.Year },
    { long: Interval.Month },
    { long: Interval.Week },
    { long: Interval.Day },
    { long: Interval.Hour },
    { long: Interval.Minute },
    { long: Interval.Second },
    { long: Interval.Millisecond },
  ];

  // convert $size to milliseconds
  // intervals_in_seconds uses seconds (duh), convert them to milliseconds here to minimize floating point errors
  size *= INTERVALS_IN_SECONDS[timeScale] * 1000;

  const strings = [];

  // after first value >= 1 print only $decimals more
  let decrementDecimals = false;
  let decimalsCount = 0;

  if (decimals !== null || decimals !== undefined) {
    decimalsCount = decimals as number;
  }

  for (let i = 0; i < units.length && decimalsCount >= 0; i++) {
    const interval = INTERVALS_IN_SECONDS[units[i].long] * 1000;
    const value = size / interval;
    if (value >= 1 || decrementDecimals) {
      decrementDecimals = true;
      const floor = Math.floor(value);
      const unit = units[i].long + (floor !== 1 ? 's' : '');
      strings.push(floor + ' ' + unit);
      size = size % interval;
      decimalsCount--;
    }
  }

  return strings.join(', ');
}

export function toClock(size: number, decimals?: DecimalCount) {
  if (size === null) {
    return '';
  }

  // < 1 second
  if (size < 1000) {
    return toUtc(size).format('SSS\\m\\s');
  }

  // < 1 minute
  if (size < 60000) {
    let format = 'ss\\s:SSS\\m\\s';
    if (decimals === 0) {
      format = 'ss\\s';
    }
    return toUtc(size).format(format);
  }

  // < 1 hour
  if (size < 3600000) {
    let format = 'mm\\m:ss\\s:SSS\\m\\s';
    if (decimals === 0) {
      format = 'mm\\m';
    } else if (decimals === 1) {
      format = 'mm\\m:ss\\s';
    }
    return toUtc(size).format(format);
  }

  let format = 'mm\\m:ss\\s:SSS\\m\\s';

  const hours = `${('0' + Math.floor(duration(size, 'milliseconds').asHours())).slice(-2)}h`;

  if (decimals === 0) {
    format = '';
  } else if (decimals === 1) {
    format = 'mm\\m';
  } else if (decimals === 2) {
    format = 'mm\\m:ss\\s';
  }

  return format ? `${hours}:${toUtc(size).format(format)}` : hours;
}

export function toDurationInMilliseconds(size: number, decimals: DecimalCount) {
  return toDuration(size, decimals, Interval.Millisecond);
}

export function toDurationInSeconds(size: number, decimals: DecimalCount) {
  return toDuration(size, decimals, Interval.Second);
}

export function toDurationInHoursMinutesSeconds(size: number) {
  const strings = [];
  const numHours = Math.floor(size / 3600);
  const numMinutes = Math.floor((size % 3600) / 60);
  const numSeconds = Math.floor((size % 3600) % 60);
  numHours > 9 ? strings.push('' + numHours) : strings.push('0' + numHours);
  numMinutes > 9 ? strings.push('' + numMinutes) : strings.push('0' + numMinutes);
  numSeconds > 9 ? strings.push('' + numSeconds) : strings.push('0' + numSeconds);
  return strings.join(':');
}

export function toTimeTicks(size: number, decimals: DecimalCount, scaledDecimals: DecimalCount) {
  return toSeconds(size, decimals, scaledDecimals);
}

export function toClockMilliseconds(size: number, decimals: DecimalCount) {
  return toClock(size, decimals);
}

export function toClockSeconds(size: number, decimals: DecimalCount) {
  return toClock(size * 1000, decimals);
}

export function dateTimeAsIso(value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean) {
  const time = isUtc ? toUtc(value) : dateTime(value);

  if (dateTime().isSame(value, 'day')) {
    return time.format('HH:mm:ss');
  }
  return time.format('YYYY-MM-DD HH:mm:ss');
}

export function dateTimeAsUS(value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean) {
  const time = isUtc ? toUtc(value) : dateTime(value);

  if (dateTime().isSame(value, 'day')) {
    return time.format('h:mm:ss a');
  }
  return time.format('MM/DD/YYYY h:mm:ss a');
}

export function dateTimeFromNow(value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean) {
  const time = isUtc ? toUtc(value) : dateTime(value);
  return time.fromNow();
}
