
// import interfaceEs6 from 'interface-es6';
import { Util, Prop } from '../shared';
import common from './common';
import g2Creator from './g2Creator';
import configMerge from './configMerge';
import EventUtil from './event';

const COORD_FUNC_PROPS = common.COORD_FUNC_PROPS;
const GEOM_FUNC_PROPS = common.GEOM_FUNC_PROPS;

const iUpdate = {
  needRebuildChart(config) {
    if (config.chart.props == null || config.chart.updateProps == null) return false;
    const chartProps = config.chart.props;
    const nextChartProps = config.chart.updateProps;

    if (!Util.shallowEqual(chartProps.padding, nextChartProps.padding)
      || !Util.shallowEqual(chartProps.background, nextChartProps.background)
      || !Util.shallowEqual(chartProps.plotBackground, nextChartProps.plotBackground)
      || !Util.shallowEqual(chartProps.pixelRatio, nextChartProps.pixelRatio)
    ) return true;

    return false;
  },

  needReExecute(config) {
    const geoms = config.geoms;

    if (geoms == null) return false;

    for (const id in geoms) {
      if (geoms[id].props && geoms[id].updateProps
        &&
        (geoms[id].props.type !== geoms[id].updateProps.type
         || (geoms[id].props.color && !geoms[id].updateProps.color)
         || (geoms[id].props.size && !geoms[id].updateProps.size)
         || (geoms[id].props.shape && !geoms[id].updateProps.shape)
        )
      ) return true;
    }
    return false;
    // return Object.keys(geoms).find((id) => {
    //   if (!geoms[id].props || !geoms[id].updateProps) return false;
    //   return geoms[id].props.type !== geoms[id].updateProps.type;
    // });
  },

  needRepaint(config) {
    const chartConfig = config.chart;
    const { data } = chartConfig.props;
    const { data: nextData } = chartConfig.updateProps;
    return data === nextData;
  },

  synchronizeG2Update(chart, config) {
    this.updateChart(chart, config.chart);
    this.updateAxises(chart, config.axises);
    this.updateTooltip(chart, config);
    this.updateCoord(chart, config);
    this.updateLegends(chart, config.legends);
    this.updateGeoms(chart, config.geoms);
    this.updateGuide(chart, config.guide);
    this.updateFacet(chart, config);
    this.updateViews(chart, config);
  },

  updateChart(chart, chartConfig) {
    if (!chartConfig) return;

    const props = chartConfig.props;
    const nextProps = chartConfig.updateProps;
    const { width, height, animate, data, scale } = props;
    const { width: nextWidth, height: nextHeight, animate: nextAnimate, data: nextData,
      scale: nextScale } = nextProps;

    if (data !== nextData) {
      chart.changeData(nextData);
    }

    if (!Util.shallowEqual(scale, nextScale)) {
      if (Util.isArray(nextScale)) {
        chart.scale(nextScale[0], nextScale[1]);
      } else {
        chart.scale(nextScale);
      }
    }

    if (animate !== nextAnimate) {
      chart.animate(nextAnimate);
    }

    if (width !== nextWidth && height !== nextHeight) {
      chart.changeSize(nextWidth, nextHeight);
    } else if (width !== nextWidth) {
      chart.changeWidth(nextWidth);
    } else if (height !== nextHeight) {
      chart.changeHeight(nextHeight);
    }

    EventUtil.updateEvents(chart, EventUtil.chartEvents, chartConfig.props, nextProps);
    EventUtil.updateBaseEvents(chart, chartConfig.updateProps, nextProps);
  },

  updateAxis(chart, axisConfig) {
    const { name, visible, ...others } = axisConfig.props;
    const { name: nextName, visible: nextVisible, ...nextOthers } = axisConfig.updateProps;

    // warning(name === nextName, '`name` propertry should not be changed in `<Axis />`');

    if (visible !== nextVisible) {
      chart.axis(name, !!nextVisible);
    }

    // todo others have g2Instance
    if (!Util.shallowEqual(others, nextOthers)) {
      chart.axis(name, nextOthers);
    }
  },

  updateAxises(chart, axises) {
    if (!axises) return;
    for (const id in axises) {
      if (axises[id] && axises[id].props && axises[id].updateProps) {
        this.updateAxis(chart, axises[id]);
      }
    }
  },

  updateTooltip(chart, config) {
    if (!config.tooltip) return;
    const props = config.tooltip.props;
    const nextProps = config.tooltip.updateProps;

    if (props == null && nextProps == null) {
      return;
    }

    if (!Util.shallowEqual(props, nextProps)) {
      chart.tooltip({ ...nextProps });
    }
  },

  updateCoord(chart, config) {
    const coordConfig = config.coord;
    if (!coordConfig) return;

    const props = coordConfig.props;
    const nextProps = coordConfig.updateProps;

    if (props == null || nextProps == null) {
      return;
    }

    // type can not be in coord's second param.
    const nextAttrs = Util.without(nextProps, COORD_FUNC_PROPS.concat(['type']));

    if (!Util.shallowEqual(props, nextProps)) {
      const g2Instance = chart.coord(nextProps.type, nextAttrs);
      coordConfig.g2Instance = g2Instance;
      Prop.init(COORD_FUNC_PROPS, nextProps, (value, key) => {
        if (key === 'reflect') {
          Util.each(value, v => g2Instance[key](v));
        } else if (key === 'transpose') {
          if (value[0] === true) g2Instance[key](...value);
        } else {
          g2Instance[key](...value);
        }
      });
    }
  },

  updateLegend(chart, legendConfig) {
    const props = legendConfig.props;
    const nextProps = legendConfig.updateProps;

    if (!nextProps) return;

    if (Util.shallowEqual(props, nextProps)) {
      return;
    }

    const { name, visible, ...cfg } = nextProps;
    const arg = !visible ? visible : cfg;

    chart.legend(...(name ? [name, arg] : [arg]));
  },

  updateLegends(chart, legends) {
    if (legends == null) {
      return;
    }

    for (const id in legends) {
      if (legends[id]) {
        this.updateLegend(chart, legends[id]);
      }
    }
  },

  updateLabel(geom, props, nextProps) {
    if (props == null || nextProps == null) {
      return;
    }

    const { content, ...others } = props;
    const { content: nextContent, ...nextOthers } = nextProps;

    if (!Util.shallowEqual(others, nextOthers)
        || !Util.shallowEqual(content, nextContent)
    ) {
      if (Util.isArray(nextContent)) {
        geom.label(nextContent[0], nextContent[1], nextOthers);
      } else {
        geom.label(nextContent, nextOthers);
      }
    }
  },

  updateGeom(chart, geomConfig) {
    const props = geomConfig.props;
    const nextProps = geomConfig.updateProps;

    if (!props || !nextProps) return;

    if (props.type !== nextProps.type) {
      // needReExecute chart
      return;
    }
    const geom = geomConfig.g2Instance;
    if (Util.shallowEqual(props, nextProps)) {
      if (geomConfig.label) {
        this.updateLabel(geom, geomConfig.label.props, geomConfig.label.updateProps);
      }
      return;
    }
    const { adjust, ...attrs } = props;
    const { adjust: nextAdjust, ...nextAttrs } = nextProps;

    if (adjust || nextAdjust) {
      geom.adjust(nextAdjust);
    }
    Prop.update(GEOM_FUNC_PROPS, attrs, nextAttrs, (value, key) => {
      geom[key](...value);
    });

    if (geomConfig.label) {
      this.updateLabel(geom, geomConfig.label.props, geomConfig.label.updateProps);
    }
  },

  updateGeoms(chart, geoms) {
    if (geoms == null) {
      return false;
    }

    for (const id in geoms) {
      if (geoms[id]) {
        this.updateGeom(chart, geoms[id]);
      }
    }

    return false;
  },

  isTypedGuideChanged(config) {
    if (!Util.shallowEqual(config.props, config.updateProps)) {
      return true;
    }

    return false;
  },

  updateGuide(chart, guide) {
    if (!guide || !guide.elements) {
      return;
    }

    const guides = guide.elements;
    let needRebuildGuide = false;

    for (const id in guides) {
      if (guides[id]) {
        if (guides[id].updateProps || this.isTypedGuideChanged(guides[id])) {
          needRebuildGuide = true;
          break;
        }
      }
    }

    if (needRebuildGuide) {
      configMerge.mergeGuide(guide, true);
      chart.guide().clear();
      g2Creator.guide(chart, guide);
    }
  },

  updateView(chart, viewInfo) {
    if (!viewInfo || !viewInfo.props || !viewInfo.updateProps || viewInfo.parentInfo.name === 'Facet') { return; }
    const view = viewInfo.g2Instance;
    const props = viewInfo.props;
    const nextProps = viewInfo.updateProps;
    /*
       Others object must exclude geoms property.
       Because geoms property will cover the g2 view' inner geoms property.
    */
    const { scale, data, animate, axis, filter } = props;
    const { scale: nextScale, animate: nextAnimate, data: nextData,
      axis: nextAxis, filter: nextFilter }
      = nextProps;

    if (animate !== nextAnimate) {
      view.animate(nextAnimate);
    }

    if (data !== nextData) {
      view.changeData(nextData);
    }

    if (!Util.shallowEqual(scale, nextScale)) {
      view.scale(nextScale);
    }

    if (!Util.shallowEqual(filter, nextFilter)) {
      nextFilter.forEach((filterArg) => {
        view.filter(filterArg[0], filterArg[1]);
      });
    }

    if (axis !== nextAxis) {
      view.axis(nextAxis);
    }

    this.updateCoord(view, viewInfo);
    this.updateAxises(view, viewInfo.axises);
    this.updateGeoms(view, viewInfo.geoms);
    this.updateGuide(view, viewInfo.guide);
  },

  updateViews(chart, config) {
    const views = config.views;

    if (!views) return;

    for (const id in views) {
      const curView = views[id];
      if (curView && (curView.needReExecute || this.needReExecute(curView))) {
        g2Creator.synchronizeG2View(curView.g2Instance, curView);
        views[id].needReExecute = false;
      } else {
        this.updateView(chart, curView);
      }
    }
  },

  updateFacet(chart, config) {
    const facetConfig = config.facet;
    if (!facetConfig) return;
    const props = facetConfig.props;
    const nextProps = facetConfig.updateProps;
    if (props == null || nextProps == null) return;

    const { type, ...others } = props;
    const { type: nextType, ...nextOthers } = nextProps;

    if (type !== nextType || !Util.shallowEqual(others, nextOthers)) {
      facetConfig.props = nextProps;
      g2Creator.facet(chart, config);
    }
  },

};

export default iUpdate;
