import { getLayer } from "../openlayers-utils/layer-utilities";
import v4 from "uuid/v4";
import { createSelector } from "redux-bundler";
import scaleToMapZoom from "../utils/scale-to-zoom-level";

/**
 * Private methods basically
 */

const createNode = (options, map) => {
  if (!options.type) options.type = options.serviceType || "Folder";

  if (!options.uid) options.uid = v4();

  if (!options.minzoom) options.minzoom = scaleToMapZoom(options.maxscale, 0);
  if (!options.maxzoom) options.maxzoom = scaleToMapZoom(options.minscale, 20);

  const node = {
    id: options.id,
    teamDatasetId: options.teamDatasetId,
    uid: options.uid,
    parent: null,
    children: [],
    root: options.root || false,
    leaf: options.type !== "Folder",
    collapsed: options.collapsed || true,
    visible: options.visible || false,
    sortOrder: options.layerOrder || 0,
    displayName: options.displayName,
    type: options.type,
    geometryType: options.geometryType,
    layerList: options.layerList,
    mapLayer:
      options.type !== "Folder"
        ? options.mapLayer || getLayer(options, null, map)
        : null,
    labelsOn: options.labelsOn ? options.labelsOn : !!options.labelDefault,
    labelDefault: !!options.labelDefault,
    labelEnabled: !!options.labelEnabled,
    url: options.url,
    info: options.info,
    identifyType: options.identifyType,
    identifyFields: options.identifyFields,
    legend: options.legend,
    passFilter: true,
    zoomTo: !!options.zoomTo,
    minzoom: options.minzoom,
    maxzoom: options.maxzoom,
    styleFn: options.styleFn,
    favorite: false,
    userDefaultLayer: false,
    selectAllLayers: options.selectAllLayers,
  };
  return node;
};

const isParentOf = (queryNode, destNode) => {
  let ancestors = [];
  function familyTree(node) {
    if (node.parent) {
      ancestors.push(node.parent);
      familyTree(node.parent);
    }
  }
  familyTree(destNode);
  return ancestors.indexOf(queryNode) !== -1;
};

const removeFromParent = (node) => {
  const parent = node.parent;
  const idx = parent.children.indexOf(node);
  parent.children.splice(idx, 1);
};

const insertNodeAt = (node, parent, idx) => {
  node.parent = parent;
  parent.children.splice(idx, 0, node);
};

/**
 *  Start store bundle config
 */

export default {
  defaultParentUid: null,
  name: "layers",

  getReducer() {
    const initialData = {
      appTime: null,
      layers: {},
      idMap: {},
      resetNeeded: false,
      filterString: "",
      showFilter: false,
      favoriteLayers: [],
      userDefaultLayers: [],
    };

    return (state = initialData, { type, payload }) => {
      if (type === "LAYERS_LIST_UPDATED") {
        return Object.assign({}, state, {
          appTime: new Date(),
          layers: payload,
          resetNeeded: true,
        });
      }

      if (type === "LAYERS_IDMAP_UPDATED") {
        return Object.assign({}, state, {
          appTime: new Date(),
          idMap: payload,
        });
      }

      if (type === "LAYERS_ORDER_UPDATED") {
        return Object.assign({}, state, {
          resetNeeded: false,
        });
      }

      if (type === "LAYERS_TOGGLE_FILTER") {
        return Object.assign({}, state, payload);
      }

      if (type === "LAYERS_FILTER_CHANGED") {
        return Object.assign({}, state, payload);
      }

      if (type === "LAYERS_FAVORITES_LIST_UPDATED") {
        return Object.assign({}, state, payload);
      }

      if (type === "LAYERS_USER_DEFAULT_LAYERS_LIST_UPDATED") {
        return Object.assign({}, state, payload);
      }

      return state;
    };
  },

  doLayersToggleFilter:
    (showFilter) =>
    ({ dispatch }) => {
      dispatch({
        type: "LAYERS_TOGGLE_FILTER",
        payload: {
          showFilter: showFilter,
        },
      });
    },

  doLayersUpdateFilter:
    (filterString) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      //const filter = new RegExp(filterString, "ig");

      function matchDownstream(n) {
        n.passFilter = true;
        if (n.children && n.children.length) {
          n.children.forEach(matchDownstream);
        }
      }

      function matchUpstream(n) {
        n.passFilter = true;
        if (!!filterString) n.collapsed = false;
        if (n.parent) {
          matchUpstream(n.parent);
        }
      }

      Object.keys(layers).forEach((uid) => {
        const lyr = layers[uid];
        //const hasString = filter.test(lyr.displayName);
        const hasString =
          lyr.displayName.toUpperCase().indexOf(filterString.toUpperCase()) !==
          -1;

        if (hasString) {
          lyr.passFilter = true;
        } else {
          lyr.passFilter = false;
        }
      });

      Object.keys(layers).forEach((uid) => {
        const lyr = layers[uid];
        if (lyr.passFilter) {
          matchDownstream(lyr);
          matchUpstream(lyr);
        }
      });
      console.log("filtered layers", layers);

      dispatch({
        type: "LAYERS_FILTER_CHANGED",
        payload: {
          filterString: filterString,
          layers: Object.assign({}, layers),
        },
      });
    },

  doAddLayer:
    (options) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const idMap = store.selectIdMap();
      const map = store.selectMap();

      const node = createNode(options, map);

      if (options.id) {
        const newIdMap = Object.assign({}, idMap, {
          [options.id]: node.uid,
        });
        dispatch({ type: "LAYERS_IDMAP_UPDATED", payload: newIdMap });
      }

      if (options.parentUid) {
        const parent = layers[options.parentUid];
        node.parent = parent;
        parent.children.push(node);
      }

      if (node.mapLayer) {
        map.addLayer(node.mapLayer);
        console.log("tried to add layer with zoomto = ", node.zoomTo);
        if (node.zoomTo) {
          const src = node.mapLayer.getSource();
          map.getView().fit(src.getExtent());
        }
      }

      const newLayers = Object.assign({}, layers, {
        [node.uid]: node,
      });

      dispatch({ type: "LAYERS_LIST_UPDATED", payload: newLayers });
    },

  doAddLayersBulk:
    (optionsCollection) =>
    ({ dispatch, store }) => {
      const root = store.selectTreeRootNode();
      const layers = store.selectLayers();
      const idMap = store.selectIdMap();
      const map = store.selectMap();

      for (var i = 0; i < optionsCollection.length; i++) {
        const options = optionsCollection[i];
        if (!options || !options.id) break;
        const node = createNode(options, map);
        idMap[options.id] = node.uid;
        layers[node.uid] = node;
        if (node.mapLayer) {
          map.addLayer(node.mapLayer);
        }
      }

      dispatch({
        type: "LAYERS_IDMAP_UPDATED",
        payload: Object.assign({}, idMap),
      });

      for (var i = 0; i < optionsCollection.length; i++) {
        const options = optionsCollection[i];
        if (!options || !options.id) break;
        let node = layers[idMap[options.id]];
        let parent;
        if (!options.parentId) {
          parent = root;
        } else {
          parent = layers[idMap[options.parentId]];
        }
        if (parent && parent.hasOwnProperty("uid")) {
          node.parent = parent;
          parent.children.push(node);
        }
      }

      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doTreeInsertAfter:
    (nodeId, destId) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      const destNode = layers[destId];
      if (!isParentOf(node, destNode)) {
        removeFromParent(node);
        const parent = destNode.parent;
        const idx = parent.children.indexOf(destNode) + 1;
        insertNodeAt(node, parent, idx);
      }
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doTreeInsertBefore:
    (nodeId, destId) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      const destNode = layers[destId];
      if (!isParentOf(node, destNode)) {
        removeFromParent(node);
        const parent = destNode.parent;
        const idx = parent.children.indexOf(destNode);
        insertNodeAt(node, parent, idx);
      }
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doTreeInsertChild:
    (nodeId, destId) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      const destNode = layers[destId];
      if (!isParentOf(node, destNode)) {
        removeFromParent(node);
        const parent = destNode;
        insertNodeAt(node, parent, parent.children.length);
      }
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doTreeMerge:
    (nodeId, destId) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      const destNode = layers[destId];

      if (!isParentOf(node, destNode)) {
        const parent = destNode.parent;
        const idx = parent.children.indexOf(destNode);

        removeFromParent(node);

        const newFolder = createNode(
          {
            root: false,
            leaf: false,
            teamDatasetId: null,
            collapsed: false,
            visible: false,
            sortOrder: 0,
            displayName: "New Folder",
            type: "Folder",
          },
          map
        );

        node.parent = newFolder;
        destNode.parent = newFolder;

        newFolder.children.push(destNode);
        newFolder.children.push(node);
        newFolder.parent = parent;
        parent.children.splice(idx, 1, newFolder);
      }
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doTreeRemove:
    (nodeId) =>
    ({ dispatch, store }) => {
      const map = store.selectMap();
      const layers = store.selectLayers();
      const node = layers[nodeId];
      function rm(n) {
        if (n) {
          if (n.children && n.children.length) {
            n.children.forEach(function (c) {
              rm(c);
            });
          }
          if (n.mapLayer) {
            map.removeLayer(n.mapLayer);
          }
          removeFromParent(n);
          delete layers[n.uid];
        }
      }
      rm(node);
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
      dispatch({ type: "LAYER_REMOVED", payload: { uid: nodeId } });
    },

  doTreeUpdate:
    (nodeId, updateObj) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      Object.keys(updateObj).forEach((key) => {
        if (key !== "uid") {
          node[key] = updateObj[key];
        }
      });
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doTreeSetCollapsedAll:
    (nodeId, collapsed) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      function setCollapsedDownstream(n) {
        n.collapsed = collapsed;
        if (n.children.length > 0) {
          n.children.forEach(setCollapsedDownstream);
        }
      }
      setCollapsedDownstream(node);
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doTreeSetCollapsed:
    (nodeId, collapsed) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      node.collapsed = collapsed;
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doTreeSetVisibleAll:
    (nodeId, visible) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      function setVisibleDownstream(n) {
        if (n.mapLayer) {
          n.mapLayer.setVisible(visible);
          n.visible = visible;
        }
        if (n.children.length > 0) {
          n.children.forEach(setVisibleDownstream);
        }
      }
      setVisibleDownstream(node);
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doLayerSetVisible:
    (nodeId, visible) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      if (node.mapLayer) {
        node.visible = visible;
        node.mapLayer.setVisible(visible);
      }
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doLayerSetLabels:
    (nodeId, labelsOn) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];

      function formatLabeledUrl(url) {
        const delim = url.indexOf("?") === -1 ? "?" : "&";
        return `${url}${delim}label=true`;
      }

      if (node.mapLayer) {
        node.labelsOn = labelsOn;
        const src = node.mapLayer.getSource();
        const urls = src.getUrls();
        const newUrls = urls.map((url) => {
          return !labelsOn ? node.url : formatLabeledUrl(node.url);
        });
        src.setUrls(newUrls);
      }

      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doLayerSetLayerOrder:
    (nodeId, sortOrder) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      if (node.mapLayer) {
        node.sortOrder = sortOrder;
        node.mapLayer.setZIndex(sortOrder);
      }
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doLayerSetOpactiy:
    (nodeId, opacity) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      if (node.mapLayer) node.mapLayer.setOpacity(opacity);
      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },

  doUpdateLayerOrder:
    () =>
    ({ dispatch, store }) => {
      const root = store.selectTreeRootNode();
      let o = 999;
      function setOrder(node) {
        let sortOrder = o--;
        if (node.mapLayer) {
          node.sortOrder = sortOrder;
          node.mapLayer.setZIndex(sortOrder);
        }
        if (node.children) {
          node.children.forEach(setOrder);
        }
      }
      setOrder(root);
      dispatch({ type: "LAYERS_ORDER_UPDATED" });
    },
  doUpdateFavoriteLayers:
    (type, lyr) =>
    ({ dispatch, store }) => {
      const favoriteLayers = store.selectFavoriteLayers();
      const layers = store.selectLayers();
      const node = layers[lyr.uid];

      if (type === "add") {
        favoriteLayers.push(node);
      } else {
        // remove layer from list
        const lyrIdx = favoriteLayers.findIndex(function (item) {
          return item.uid === lyr.uid;
        });
        if (lyrIdx !== -1) {
          favoriteLayers.splice(lyrIdx, 1);
        }
      }

      dispatch({
        type: "LAYERS_FAVORITES_LIST_UPDATED",
        payload: {
          favoriteLayers: favoriteLayers,
        },
      });
    },
  doLayerSetFavorite:
    (nodeId, favorite) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      node.favorite = favorite;

      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
    },
  
  doUpdateUserDefaultLayers:
    (type, lyr) =>
    ({ dispatch, store }) => {
      const userDefaultLayers = store.selectUserDefaultLayers();
      const layers = store.selectLayers();
      const node = layers[lyr.uid];
      
      if (type === "add") {
        userDefaultLayers.push(node);
      } else {
        const lyrIdx = userDefaultLayers.findIndex(function (item) {
          return item.uid === lyr.uid;
        });
        if (lyrIdx !== -1) {
          userDefaultLayers.splice(lyrIdx, 1);
        }
      }

      dispatch({
        type: "LAYERS_USER_DEFAULT_LAYERS_LIST_UPDATED",
        payload: {
          userDefaultLayers: userDefaultLayers,
        },
      });
    },
    
  doLayerSetUserDefaultLayers:
    (nodeId, userDefaultLayer) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayers();
      const node = layers[nodeId];
      node.userDefaultLayer = userDefaultLayer;

      dispatch({
        type: "LAYERS_LIST_UPDATED",
        payload: Object.assign({}, layers),
      });
  },

  selectFavoriteLayers: (state) => {
    return state.layers.favoriteLayers;
  },

  selectUserDefaultLayers: (state) => {
    return state.layers.userDefaultLayers;
  },

  selectLayers: (state) => {
    return state.layers.layers;
  },

  selectIdMap: (state) => {
    return state.layers.idMap;
  },

  selectUidMap: createSelector("selectLayers", (layers) => {
    const uidMap = {};
    Object.keys(layers).forEach((key) => {
      if (layers[key]) {
        uidMap[key] = layers[key].displayName;
      }
    });
    return uidMap;
  }),

  selectCmLayers: createSelector("selectLayers", (layers) => {
    const cmLayers = [];
    Object.keys(layers).forEach((key) => {
      if (layers[key] && layers[key].teamDatasetId)
        cmLayers.push(layers[key].teamDatasetId);
    });
    return cmLayers;
  }),

  selectVisibleLayers: createSelector("selectLayers", (layers) => {
    const visibleLayers = [];
    Object.keys(layers).forEach((key) => {
      if (layers[key] && layers[key].visible) visibleLayers.push(layers[key]);
    });
    return visibleLayers;
  }),

  selectVisibleCmLayers: createSelector("selectLayers", (layers) => {
    const visibleCmLayers = [];
    Object.keys(layers).forEach((key) => {
      if (layers[key] && layers[key].teamDatasetId && layers[key].visible)
        visibleCmLayers.push(layers[key].teamDatasetId);
    });
    return visibleCmLayers;
  }),

  selectCmLayerNames: createSelector("selectLayers", (layers) => {
    const lyrNames = [];
    Object.keys(layers).forEach((key) => {
      if (layers[key] && layers[key].teamDatasetId)
        lyrNames.push({
          teamDatasetId: layers[key].teamDatasetId,
          displayName: layers[key].displayName,
        });
    });
    return lyrNames;
  }),

  selectLayerFilterString: (state) => {
    return state.layers.filterString;
  },

  selectLayerShowFilter: (state) => {
    return state.layers.showFilter;
  },

  reactResetSortOrder: (state) => {
    if (state.layers.resetNeeded)
      return { actionCreator: "doUpdateLayerOrder" };
  },
};
