import React from "react";
import { Range } from "common/RangeInput";
import moment from "moment";
import AutoSizer from "react-virtualized-auto-sizer";
import { abbreviateNum } from "utils/numbers";
import { or } from "utils/types";
import {
  createContainer,
  ScalePropType,
  VictoryArea,
  VictoryAxis,
  VictoryChart,
  VictoryLabel,
  VictoryLegend,
  VictoryLine,
  VictoryPie,
  VictoryScatter,
  VictoryTheme,
  VictoryTooltip,
  VictoryTooltipProps
} from "victory";
import {
  ResponsiveContainer,
  RadarChart,
  PolarGrid,
  PolarAngleAxis,
  PolarRadiusAxis,
  Radar,
  Legend
} from "recharts";
import { pond } from "protobuf-ts/pond";
import { getTextPrimary, getTextSecondary } from "theme/text";
import { darken, useTheme } from "@mui/material";
import { useWidth } from "hooks";
import { useNavigate } from "react-router-dom";
const VictoryCursorVoronoiContainer: any = createContainer("cursor", "voronoi");

export enum GraphOrientation {
  GRAPH_ORIENTATION_PORTRAIT = 0,
  GRAPH_ORIENTATION_LANDSCAPE = 1
}

export enum GraphType {
  LINEAR = 0,
  AREA = 1,
  BAR = 2,
  MULTILINE = 3,
  SCATTER = 4,
  PIE = 5,
  RADAR = 6
}

export interface vXAxis {
  scale: ScalePropType;
  label: string;
}

export interface GraphPoint {
  x: any;
  y: any;
  y0?: any;
  bubble?: number;
  colour?: string;
}

export interface PieSlice {
  x: string;
  y: number;
  fill: string;
  linkToOnClick?: string;
}

export interface InteractionLine {
  value: any;
  colour: any;
}

export interface GraphComponent {
  label: string;
  data: Array<GraphPoint> | Array<Array<GraphPoint>> | Array<PieSlice>;
  overlays?: pond.ComponentOverlays[];
  tick?: string;
  colour: any;
  type: GraphType;
  range?: Range;
  interactionLines?: Array<InteractionLine>;
  isBoolean?: boolean;
  states?: Array<string>;
  isPercent?: boolean;
  xAxis?: vXAxis;
  decimals?: number;
  horizontalBars?: boolean;
}

interface CustomVictoryTooltipProps extends VictoryTooltipProps {
  height: any;
  width: any;
}

class CustomVictoryTooltip extends React.Component<CustomVictoryTooltipProps> {
  render() {
    const { x, height, width, text } = this.props;
    let y = height / 2;
    let orientation: "right" | "left" = x && x < width * 0.66 ? "right" : "left";
    let xOffset: number = orientation === "right" ? 5 : -5;
    return (
      <VictoryTooltip
        active
        x={x}
        y={y}
        dx={xOffset}
        orientation={orientation}
        pointerLength={0}
        flyoutStyle={{ stroke: "none", strokeWidth: 0, fill: "#323232" }}
        style={{ fill: "#fff" }}
        text={text}
      />
    );
  }
}

interface Props /*extends WithWidth, RouteComponentProps, WithTheme*/ {
  orientation?: GraphOrientation;
  graphComponent?: GraphComponent;
}

interface State {
  interactionComponents: Array<any>;
  dataComponent: any;
  updateCounter: number;
  overlayComponents: Array<any>;
}

//a linear graph with the option for two y-axis
class Graph extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      interactionComponents: [],
      dataComponent: null,
      updateCounter: 0,
      overlayComponents: []
    };
  }

  componentDidMount = () => {
    this.setupGraph();
  };

  componentDidUpdate = (prevProps: Props) => {
    if (prevProps.graphComponent !== this.props.graphComponent) {
      this.setupGraph();
      this.setState(prevState => ({
        updateCounter: prevState.updateCounter + 1
      }));
    }
  };

  setupGraph = () => {
    const { graphComponent } = this.props;
    if (graphComponent && or(graphComponent.data, []).length > 0) {
      let dataComponent = this.getDataComponent(graphComponent, "graphComponent");
      switch (graphComponent.type) {
        case GraphType.PIE:
          this.setState({
            interactionComponents: [],
            dataComponent: dataComponent
          });
          break;
        case GraphType.RADAR:
          this.setState({
            interactionComponents: [],
            dataComponent: dataComponent
          });
          break;
        default:
          let timeData, startDate: string, endDate: string;
          timeData =
            graphComponent.type === GraphType.MULTILINE
              ? (graphComponent.data[0] as Array<GraphPoint>)
              : (graphComponent.data as Array<GraphPoint>);

          startDate = timeData[timeData.length - 1].x;
          endDate = timeData[0].x;
          timeData.forEach((d: GraphPoint) => {
            if (moment(d.x).isBefore(moment(startDate))) {
              startDate = d.x;
            }
            if (moment(d.x).isAfter(moment(endDate))) {
              endDate = d.x;
            }
          });

          this.setState({
            interactionComponents: this.getInteractionComponents(
              graphComponent.interactionLines ? graphComponent.interactionLines : [],
              startDate,
              endDate
            ),
            dataComponent: dataComponent,
            overlayComponents: this.getOverlayComponents(startDate, endDate)
          });

          break;
      }
    }
  };

  getLegend = () => {
    const { graphComponent } = this.props;
    const textColour = getTextSecondary()

    if (!graphComponent) {
      return [];
    }
    //prepares the initial object
    var legend = [
      {
        name: graphComponent.label,
        symbol: { fill: graphComponent.colour },
        labels: { fill: textColour }
      }
    ];

    return legend;
  };

  //uses the material theme from victory as a base
  customTheme = () => {
    let graphTheme = VictoryTheme.material as any; //need to cast as 'any' because Victory has an issue with their ThemeInterface

    //removing the grid
    graphTheme.axis.style.grid = {
      fill: "none",
      stroke: "none"
    };

    graphTheme.voronoi.style.flyout = {
      ...graphTheme.voronoi.style.flyout,
      ...{ fill: "#323232", stroke: "none", strokeWidth: 0 }
    };

    graphTheme.voronoi.style.labels = {
      ...graphTheme.voronoi.style.labels,
      ...{ fill: "#fff" }
    };

    return graphTheme;
  };

  getInteractionData = (interactionLine: InteractionLine, startDate: any, endDate: any) => {
    return [
      { x: startDate, y: interactionLine.value },
      { x: endDate, y: interactionLine.value }
    ];
  };

  getInteractionComponents = (
    interactionLines: Array<InteractionLine>,
    startDate: any,
    endDate: any
  ) => {
    var interactionComponents = [];
    for (let i = 0; i < or(interactionLines, []).length; i++) {
      interactionComponents.push(
        <VictoryLine
          key={i}
          name={"interactionLine-" + i.toString()}
          style={{
            data: {
              stroke: interactionLines[i].colour,
              strokeOpacity: 0.3,
              strokeDasharray: "5, 5"
            }
          }}
          data={this.getInteractionData(interactionLines[i], startDate, endDate)}
          x="x"
          y="y"
        />
      );
    }

    return interactionComponents;
  };

  getOverlayData = (overlay: pond.ComponentOverlays, startDate: any, endDate: any) => {
    return [
      { x: startDate, y: overlay.max, y0: overlay.min },
      { x: endDate, y: overlay.max, y0: overlay.min }
    ];
  };

  getOverlayComponents = (startDate: any, endDate: any) => {
    var overlayComponents: JSX.Element[] = [];
    if (this.props.graphComponent?.overlays) {
      let o = this.props.graphComponent?.overlays;
      o.forEach((overlay, i) => {
        overlayComponents.push(
          <VictoryArea
            key={"overlay" + i}
            style={{
              data: {
                fill: overlay.colour,
                opacity: 0.15
              }
            }}
            data={this.getOverlayData(overlay, startDate, endDate)}
            x="x"
            y="y"
          />
        );
      });
    }
    return overlayComponents;
  };

  getAreaComponent = (data: Array<GraphPoint>, colour: any, key: string) => {
    return (
      <VictoryArea
        key={key}
        style={{
          data: { fill: colour, fillOpacity: 0.33 }
        }}
        data={data}
        x="x"
        y="y"
        interpolation="monotoneX"
      />
    );
  };

  // getBarComponent = (
  //   data: Array<GraphPoint>,
  //   colour: any,
  //   key: string,
  //   horizontal: boolean = false
  // ) => {
  //   return (
  //     <VictoryBar
  //       key={key}
  //       horizontal={horizontal}
  //       style={{
  //         data: {
  //           fill: (d: GraphPoint) => or(d.colour, colour),
  //           stroke: (d: GraphPoint) => or(d.colour, colour),
  //           fillOpacity: 0.6,
  //           strokeWidth: 1
  //         }
  //       }}
  //       alignment="start"
  //       barWidth={7}
  //       cornerRadius={0}
  //       data={data}
  //       x="x"
  //       y="y"
  //     />
  //   );
  // };

  //data should contain an array of arrays (lines)
  getMultilineComponent = (data: Array<Array<GraphPoint>>, colour: any, key: string) => {
    let lines = [];
    for (var i = 0; i < or(data, []).length; i++) {
      lines.push(this.getLineComponent(data[i], colour, key + "-" + i.toString()));
    }
    return lines;
  };

  getScatterComponent = (data: Array<GraphPoint>, colour: any, key: string) => {
    // const { theme } = this.props;
    return (
      <VictoryScatter
        key={key}
        maxBubbleSize={50}
        size={datum => datum.bubble * 1.5 + 5}
        style={{
          data: {
            fill: colour,
            fillOpacity: (datum: any) => (datum.bubble > 0 ? 0.65 : 0.25),
            stroke: darken(colour, 0.25),
            strokeOpacity: (datum: any) => (datum.bubble > 0 ? 0.65 : 0.25),
            strokeWidth: 1
          },
          labels: {
            fill: getTextPrimary(),
            fontSize: 10
          }
        }}
        labels={(datum: any) => (datum.bubble > 0 ? datum.bubble : "")}
        labelComponent={<VictoryLabel dy={13} />}
        data={data}
        x="x"
        y="y"
      />
    );
  };

  getRadarComponent = (data: Array<GraphPoint>, colour: any, key: string) => {
    let dataTemplate = this.getWindDirTemplate();
    return (
      <RadarChart data={this.assembleRadarData(data, dataTemplate)} key={key}>
        <Legend />
        <PolarGrid />
        <PolarAngleAxis tick={{ fill: colour }} dataKey="key" />
        <PolarRadiusAxis />
        <Radar
          name="Wind Direction"
          dataKey="value"
          stroke={colour}
          fill={colour}
          fillOpacity={0.6}
        />
      </RadarChart>
    );
  };

  assembleRadarData = (
    data: Array<GraphPoint>,
    dataTemplate: Array<{ key: string; value: number }>
  ) => {
    let aData = dataTemplate;
    data.forEach(point => {
      if (point.y !== 0) {
        aData[point.y - 1].value = aData[point.y - 1].value + 1;
      }
    });
    return aData;
  };

  getWindDirTemplate = () => {
    return [
      {
        key: "N",
        value: 0
      },
      {
        key: "NNE",
        value: 0
      },
      {
        key: "NE",
        value: 0
      },
      {
        key: "ENE",
        value: 0
      },
      {
        key: "E",
        value: 0
      },
      {
        key: "ESE",
        value: 0
      },
      {
        key: "SE",
        value: 0
      },
      {
        key: "SSE",
        value: 0
      },
      {
        key: "S",
        value: 0
      },
      {
        key: "SSW",
        value: 0
      },
      {
        key: "SW",
        value: 0
      },
      {
        key: "WSW",
        value: 0
      },
      {
        key: "W",
        value: 0
      },
      {
        key: "WNW",
        value: 0
      },
      {
        key: "NW",
        value: 0
      },
      {
        key: "NNW",
        value: 0
      }
    ];
  };

  getLineComponent = (data: Array<GraphPoint>, colour: any, key: string) => {
    return (
      <VictoryLine
        key={key}
        style={{
          data: { stroke: colour }
        }}
        data={data}
        x="x"
        y="y"
        interpolation="monotoneX"
      />
    );
  };

  //dynamically loads a data component based on type (area, bar, line), defaults to line*/
  getDataComponent(graphComponent: GraphComponent, key: string): any {
    const { type, data, colour } = graphComponent;
    switch (type) {
      case GraphType.AREA:
        return this.getAreaComponent(data as Array<GraphPoint>, colour, key);
      // case GraphType.BAR:
      //   return this.getBarComponent(
      //     data as Array<GraphPoint>,
      //     colour,
      //     key,
      //     graphComponent.horizontalBars
      //   );
      case GraphType.MULTILINE:
        return this.getMultilineComponent(data as Array<Array<GraphPoint>>, colour, key);
      case GraphType.SCATTER:
        return this.getScatterComponent(data as Array<GraphPoint>, colour, key);
      case GraphType.PIE:
        return;
      case GraphType.RADAR:
        return this.getRadarComponent(data as Array<GraphPoint>, colour, key);
      default:
        //line component is default
        return this.getLineComponent(data as Array<GraphPoint>, colour, key);
    }
  }

  getContainerHeight = () => {
    const { orientation } = this.props;
    const width = useWidth()
    if (orientation === GraphOrientation.GRAPH_ORIENTATION_LANDSCAPE) {
      switch (width) {
        case "xs":
          return 250;
        case "sm":
          return 300;
        case "md":
          return 350;
        case "lg":
          return 375;
        case "xl":
          return 400;
        default:
          return 300;
      }
    } else {
      //default portrait view
      return 300;
    }
  };

  emptyPieSlice(): PieSlice {
    // const { theme } = this.props;
    const theme = useTheme()
    return {
      x: "",
      y: 1,
      fill: theme.palette.grey.A700
    } as PieSlice;
  }

  // fillCallback: VictoryStringOrNumberCallback = (d: PieSlice) => {
  //   return d.fill
  // }

  getPieChart(width: any, height: any) {
    const { graphComponent } = this.props;
    const theme = useTheme()
    const navigate = useNavigate()
    const { data } = graphComponent ? graphComponent : { data: [] };
    const { label } = graphComponent ? graphComponent : { label: "" };
    const legendData = (data as PieSlice[]).map(d => {
      return {
        name: d.x + " (" + d.y + ")",
        symbol: { fill: d.fill },
        labels: { fill: theme.palette.text.primary, fontSize: width >= 300 ? 16 : 14 }
      };
    });

    //filter out zero-slices
    const filteredData: PieSlice[] = (data as PieSlice[]).filter(d => d.y > 0);
    let hasActions = false;
    filteredData.forEach(d => {
      if (d.linkToOnClick) hasActions = true;
    });

    //pie chart is empty
    if (filteredData.length <= 0) {
      filteredData.push(this.emptyPieSlice());
    }

    return (
      <svg width={width} height={height}>
        <VictoryLegend
          standalone={false}
          gutter={10}
          orientation="horizontal"
          itemsPerRow={width >= 600 ? 3 : width >= 300 ? 2 : 1}
          data={legendData}
        />
        <VictoryPie
          standalone={false}
          data={filteredData}
          x="x"
          y="y"
          style={{
            data: {
              // fill: (d: PieSlice): VictoryStringOrNumberCallback => {
              //   return (d.fill)
              // },
              cursor: hasActions ? "pointer" : "default"
            },
            labels: {
              fontSize: "16px",
              fill: theme.palette.text.primary
            }
          }}
          height={height}
          width={width}
          padAngle={2}
          innerRadius={height * 0.25}
          padding={{
            top: height * 0.25,
            right: 0,
            bottom: height * 0.1,
            left: 0
          }}
          labels={() => ""}
          events={[
            {
              target: "data",
              eventHandlers: {
                onClick: () => ({
                  mutation: (props: any) => {
                    // const { history } = this.props;
                    const { datum } = props;

                    if (datum.linkToOnClick) {
                      navigate(datum.linkToOnClick);
                    }
                  }
                }),
                onMouseOver: () => ({
                  mutation: props => ({
                    radius: hasActions ? props.radius * 1.05 : props.radius
                  })
                }),
                onMouseOut: () => ({
                  mutation: () => null
                })
              }
            }
          ]}
        />
        {label && label !== "" && (
          <VictoryLabel
            textAnchor="middle"
            style={{ fontSize: 24, fill: theme.palette.text.primary }}
            x={width / 2}
            y={(height / 2) * 1.15}
            text={label}
          />
        )}
      </svg>
    );
  }

  render() {
    const theme = useTheme()
    const { graphComponent } = this.props;
    const { interactionComponents, overlayComponents, dataComponent, updateCounter } = this.state;

    if (!graphComponent || !graphComponent.data) {
      return null;
    }

    const { range, isBoolean, isPercent } = graphComponent;
    const textColour = theme.palette.text.secondary;
    const legend = this.getLegend();
    const height = this.getContainerHeight();

    const hideCursor: boolean = [GraphType.BAR].includes(graphComponent.type);
    const disableCursorVoronoi: boolean = [GraphType.AREA, GraphType.MULTILINE].includes(
      graphComponent.type
    );

    return (
      <AutoSizer disableHeight key={updateCounter}>
        {({ width }: any) => (
          <React.Fragment>
            {/* Don't need the entire graph for pie charts */}
            {graphComponent.type !== GraphType.PIE ? (
              <React.Fragment>
                {/*
                // @ts-ignore */}
                {graphComponent.type === GraphType.RADAR ? (
                  <ResponsiveContainer minHeight={450} width={width}>
                    {dataComponent}
                  </ResponsiveContainer>
                ) : (
                  <VictoryChart
                    theme={this.customTheme()}
                    containerComponent={
                      <VictoryCursorVoronoiContainer
                        voronoiDimension="x"
                        cursorDimension="x"
                        disable={width === "xs" || disableCursorVoronoi}
                        responsive={false}
                        cursorComponent={
                          hideCursor ? (
                            <React.Fragment />
                          ) : (
                            <line
                              style={{
                                stroke: "#323232",
                                strokeWidth: 2
                              }}
                            />
                          )
                        }
                        labels={(datum: any) => {
                          if (
                            graphComponent &&
                            or<vXAxis>(graphComponent.xAxis, { scale: "time" } as vXAxis).scale ===
                              "time"
                          ) {
                            let decimals = graphComponent.decimals ? graphComponent.decimals : 0;
                            return `${moment(or(datum.x, undefined)).calendar()}, ${datum.y.toFixed(
                              decimals
                            )}`;
                          }
                          return datum.y;
                        }}
                        labelComponent={
                          hideCursor ? (
                            <VictoryTooltip />
                          ) : (
                            <CustomVictoryTooltip width={width} height={height} />
                          )
                        }
                        style={{ touchAction: "auto" }}
                        voronoiBlacklist={["interactionLine"]}
                      />
                    }
                    animate={{ duration: 1000, easing: "expInOut", onLoad: { duration: 500 } }}
                    scale={{
                      x: or(graphComponent.xAxis, { scale: "time" }).scale,
                      y: "linear"
                    }}
                    width={width}
                    height={height}
                    padding={{ top: 30, bottom: 30, left: 60, right: 60 }}
                    // @ts-ignore */}
                    minDomain={{
                      y:
                        range && range.min !== undefined
                          ? range.min
                          : isBoolean || isPercent
                          ? 0
                          : undefined
                    }}
                    maxDomain={{
                      y:
                        range && range.max !== undefined
                          ? range.max
                          : isPercent
                          ? 100
                          : isBoolean
                          ? 1
                          : undefined
                    }}
                    domainPadding={isBoolean || isPercent ? undefined : { y: 50 }}>
                    <VictoryLegend
                      x={75}
                      centerTitle
                      orientation="horizontal"
                      style={{ labels: { fontSize: 12 } }}
                      data={or(legend, { name: "" } as any)}
                    />

                    {/* X Axis */}
                    <VictoryAxis
                      crossAxis
                      style={{
                        axis: { stroke: textColour },
                        ticks: { stroke: textColour, fontSize: 7 },
                        tickLabels: { fontSize: 12, fill: textColour },
                        axisLabel: { fontSize: 12, fill: textColour }
                      }}
                      standalone={false}
                      fixLabelOverlap={true}
                      orientation="bottom"
                      label={or(graphComponent.xAxis, { label: "" }).label}
                    />
                    {/* Y Axis */}
                    <VictoryAxis
                      crossAxis={false}
                      style={{
                        axis: { stroke: textColour },
                        ticks: { stroke: textColour, fontSize: 4 },
                        tickLabels: {
                          fontSize: 12,
                          padding: 1,
                          fill: textColour
                        }
                      }}
                      dependentAxis
                      standalone={false}
                      fixLabelOverlap={true}
                      tickValues={graphComponent.isBoolean ? [0, 1] : undefined}
                      tickFormat={(tick: any) =>
                        graphComponent.isBoolean
                          ? tick === 1
                            ? or(graphComponent.states, ["Off", "On"])[1]
                            : or(graphComponent.states, ["Off", "On"])[0]
                          : `${abbreviateNum(tick)}` + or(graphComponent.tick, "")
                      }
                    />
                    {interactionComponents}
                    {overlayComponents}
                    {dataComponent}
                  </VictoryChart>
                )}
              </React.Fragment>
            ) : (
              this.getPieChart(width, height)
            )}
          </React.Fragment>
        )}
      </AutoSizer>
    );
  }
}

export default ((Graph));
