const randomColor = require('randomcolor');
const chalk = require('chalk');

module.exports = { setupTaskDisplay, displayChart };

const SYMBOLS = {
  Empty: '',
  Space: ' ',
  Full: '█',
  SevenEighths: '▉',
  ThreeQuarters: '▊',
  FiveEighths: '▋',
  Half: '▌',
  ThreeEighths: '▍',
  Quarter: '▎',
  Eighth: '▏',
  RightHalf: '▐',
  RightEighth: '▕',
};

function setupTaskDisplay(taskEvents) {
  const taskData = [];
  taskEvents.on('start', ([name]) => {
    console.log(`Starting '${name}'...`);
  });
  taskEvents.on('end', ([name, start, end]) => {
    taskData.push([name, start, end]);
    console.log(`Finished '${name}'`);
  });
  taskEvents.on('complete', () => {
    displayChart(taskData);
  });
}

function displayChart(data) {
  // sort tasks by start time
  data.sort((a, b) => a[1] - b[1]);

  // get bounds
  const first = Math.min(...data.map((entry) => entry[1]));
  const last = Math.max(...data.map((entry) => entry[2]));

  // get colors
  const colors = randomColor({ count: data.length });

  // some heading before the bars
  console.log(`\nBuild completed. Task timeline:`);

  // build bars for bounds
  data.forEach((entry, index) => {
    const [label, start, end] = entry;
    const [start2, end2] = [start, end].map((value) =>
      adjust(value, first, last, 40),
    );
    const barString = barBuilder(start2, end2);
    const color = colors[index];
    const coloredBarString = colorize(color, barString);
    const duration = ((end - start) / 1e3).toFixed(1);
    console.log(coloredBarString, `${label} ${duration}s`);
  });
}

function colorize(color, string) {
  const colorizer =
    typeof chalk[color] === 'function' ? chalk[color] : chalk.hex(color);
  return colorizer(string);
}

// scale number within bounds
function adjust(value, first, last, size) {
  const length = last - first;
  const result = ((value - first) / length) * size;
  return result;
}

// draw bars
function barBuilder(start, end) {
  const [spaceInt, spaceRest] = splitNumber(start);
  const barBodyLength = end - spaceInt;
  let [barInt, barRest] = splitNumber(barBodyLength);
  // We are handling zero value as a special case
  // to print at least something on the screen
  if (barInt === 0 && barRest === 0) {
    barInt = 0;
    barRest = 0.001;
  }

  const spaceFull = SYMBOLS.Space.repeat(spaceInt);
  const spacePartial = getSymbolNormalRight(spaceRest);
  const barFull = SYMBOLS.Full.repeat(barInt);
  const barPartial = getSymbolNormal(barRest);

  return `${spaceFull}${spacePartial}${barFull}${barPartial}`;
}

// get integer and remainder
function splitNumber(value = 0) {
  const [int, rest = '0'] = value.toString().split('.');
  const int2 = parseInt(int, 10);
  const rest2 = parseInt(rest, 10) / Math.pow(10, rest.length);
  return [int2, rest2];
}

// get partial block char for value (left-adjusted)
function getSymbolNormal(value) {
  // round to closest supported value
  const possibleValues = [
    0,
    1 / 8,
    1 / 4,
    3 / 8,
    1 / 2,
    5 / 8,
    3 / 4,
    7 / 8,
    1,
  ];
  const rounded = possibleValues.reduce((prev, curr) => {
    return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev;
  });

  if (rounded === 0) {
    return SYMBOLS.Empty;
  } else if (rounded === 1 / 8) {
    return SYMBOLS.Eighth;
  } else if (rounded === 1 / 4) {
    return SYMBOLS.Quarter;
  } else if (rounded === 3 / 8) {
    return SYMBOLS.ThreeEighths;
  } else if (rounded === 1 / 2) {
    return SYMBOLS.Half;
  } else if (rounded === 5 / 8) {
    return SYMBOLS.FiveEighths;
  } else if (rounded === 3 / 4) {
    return SYMBOLS.ThreeQuarters;
  } else if (rounded === 7 / 8) {
    return SYMBOLS.SevenEighths;
  }
  return SYMBOLS.Full;
}

// get partial block char for value (right-adjusted)
function getSymbolNormalRight(value) {
  // round to closest supported value (not much :/)
  const possibleValues = [0, 1 / 2, 7 / 8, 1];
  const rounded = possibleValues.reduce((prev, curr) => {
    return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev;
  });

  if (rounded === 0) {
    return SYMBOLS.Full;
  } else if (rounded === 1 / 2) {
    return SYMBOLS.RightHalf;
  } else if (rounded === 7 / 8) {
    return SYMBOLS.RightEighth;
  } else if (rounded === 1) {
    return SYMBOLS.Space;
  }
  throw new Error('getSymbolNormalRight got unexpected result');
}
