import * as React from "react";
import * as ChartJS from "chart.js";
import { useEffect, useRef } from "react";
import merge from "lodash/merge";
import moment from "moment";
import Color from "color";
import { Chart, ChartDataSet, ChartKind, ChartProps } from "./chart";
import { ChartData, ChartOptions, ChartPoint } from "chart.js";
import { cssNames } from "../../utils";
import { bytesToUnits } from "../../utils";
import { ZebraStripes } from "./zebra-stripes.plugin";
import { themeStore } from "../../theme.store";
import { NoMetrics } from "../resource-metrics/no-metrics";

interface Props extends ChartProps {
  name?: string;
  title?: string;
  timeLabelStep?: number;  // Minute labels appearance step
}

interface ChartContext {
  chart: ChartJS;
  dataIndex?: number;
  dataset?: ChartDataSet;
}

const defaultProps: Partial<Props> = {
  timeLabelStep: 10,
  plugins: [ZebraStripes]
};

export function BarChart({
  name: _name,
  data,
  className,
  timeLabelStep,
  plugins,
  options: customOptions,
  ...settings
}: Props): JSX.Element {
  const { textColorPrimary, borderFaintColor, chartStripesColor } = themeStore.activeTheme.colors;

  const savedName = useRef<string>();

  useEffect(() => {
    savedName.current = name;
  });

  const getBarColor = (ctx: ChartContext): string => {
    const { dataset } = ctx;
    const color = dataset.borderColor;
    return Color(color).alpha(0.2).string();
  };

  // Remove empty sets and insert default data
  const chartData: ChartData = {
    ...data,
    datasets: data.datasets
      .filter(set => set.data.length)
      .map(item => {
        return {
          type: ChartKind.BAR,
          borderWidth: { top: 3 },
          barPercentage: 1,
          categoryPercentage: 1,
          ...item
        };
      })
  };

  const formatTimeLabels = (timestamp: string, index: number): string => {
    const label = moment(parseInt(timestamp)).format("HH:mm");
    const offset = "     ";
    if (index == 0) {
      return offset + label;
    }
    if (index == 60) {
      return label + offset;
    }
    return index % timeLabelStep == 0 ? label : "";
  };

  const barOptions: ChartOptions = {
    maintainAspectRatio: false,
    responsive: true,
    scales: {
      xAxes: [{
        type: "time",
        offset: true,
        gridLines: {
          display: false,
        },
        stacked: true,
        ticks: {
          callback: formatTimeLabels,
          autoSkip: false,
          source: "data",
          backdropColor: "white",
          fontColor: textColorPrimary,
          fontSize: 11,
          maxRotation: 0,
          minRotation: 0
        },
        bounds: "data",
        time: {
          unit: "minute",
          displayFormats: {
            minute: "x"
          },
          parser: (timestamp): moment.Moment => moment.unix(parseInt(timestamp))
        }
      }],
      yAxes: [{
        position: "right",
        gridLines: {
          color: borderFaintColor,
          drawBorder: false,
          tickMarkLength: 0,
          zeroLineWidth: 0
        },
        ticks: {
          maxTicksLimit: 6,
          fontColor: textColorPrimary,
          fontSize: 11,
          padding: 8,
          min: 0
        }
      }]
    },
    tooltips: {
      mode: "index",
      position: "cursor",
      callbacks: {
        title: (tooltipItems): string => {
          const now = new Date().getTime();
          if (new Date(tooltipItems[0].xLabel).getTime() > now) {
            return "";
          }
          return `${tooltipItems[0].xLabel}`;
        },
        labelColor: ({ datasetIndex }): ChartJS.ChartTooltipLabelColor => {
          return {
            borderColor: "darkgray",
            backgroundColor: chartData.datasets[datasetIndex].borderColor as string
          };
        }
      }
    },
    animation: {
      duration: 0
    },
    elements: {
      rectangle: {
        backgroundColor: getBarColor.bind(null)
      }
    },
    plugins: {
      ZebraStripes: {
        stripeColor: chartStripesColor
      }
    }
  };
  const options = merge(barOptions, customOptions);
  if (chartData.datasets.length == 0) {
    return <NoMetrics/>;
  }
  return (
    <Chart
      className={cssNames("BarChart flex box grow column", className)}
      type={ChartKind.BAR}
      data={chartData}
      options={options}
      plugins={plugins}
      {...settings}
    />
  );
}

BarChart.defaultProps = defaultProps;

// Default options for all charts containing memory units (network, disk, memory, etc)
export const memoryOptions: ChartOptions = {
  scales: {
    yAxes: [{
      ticks: {
        callback: (value: number | string): string => {
          if (typeof value == "string") {
            const float = parseFloat(value);
            if (float < 1) {
              return float.toFixed(3);
            }

            return bytesToUnits(parseInt(value));
          }

          return `${value}`;
        },
        stepSize: 1
      }
    }]
  },
  tooltips: {
    callbacks: {
      label: ({ datasetIndex, index }, { datasets }): string => {
        const { label, data } = datasets[datasetIndex];
        const value = data[index] as ChartPoint;
        return `${label}: ${bytesToUnits(parseInt(value.y.toString()), 3)}`;
      }
    }
  }
};

// Default options for all charts with cpu units or other decimal numbers
export const cpuOptions: ChartOptions = {
  scales: {
    yAxes: [{
      ticks: {
        callback: (value: number | string): string => {
          const float = parseFloat(`${value}`);
          if (float == 0) {
            return "0";
          }
          if (float < 10) {
            return float.toFixed(3);
          }
          if (float < 100) {
            return float.toFixed(2);
          }
          return float.toFixed(1);
        }
      }
    }]
  },
  tooltips: {
    callbacks: {
      label: ({ datasetIndex, index }, { datasets }): string => {
        const { label, data } = datasets[datasetIndex];
        const value = data[index] as ChartPoint;
        return `${label}: ${parseFloat(value.y as string).toPrecision(2)}`;
      }
    }
  }
};