/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import Enums from "../Enums";
import { curveBasis } from "d3-shape";

const DataUtils = {
  /**
   * [getBaseUrl description]
   * @param  {[type]} url [description]
   * @return {[type]}     [description]
   */
  getBaseUrl: function (url) {
    return url.replace(/\/[\w-]+.(jsp|html)|\/+$/gi, "");
  },
  /**
   * [getEntityIconPath description]
   * @param  {[type]} options.entityData [description]
   * @param  {Object} options.errorUrl   }            [description]
   * @return {[type]}                    [description]
   */
  getEntityIconPath: function ({ entityData, errorUrl } = {}) {
    var serviceType,
      status,
      typeName,
      iconBasePath =
        this.getBaseUrl(window.location.pathname) + Globals.entityImgPath;
    if (entityData) {
      typeName = entityData.typeName;
      serviceType = entityData && entityData.serviceType;
      status = entityData && entityData.status;
    }

    function getImgPath(imageName) {
      return (
        iconBasePath +
        (Enums.entityStateReadOnly[status]
          ? "disabled/" + imageName
          : imageName)
      );
    }

    function getDefaultImgPath() {
      if (entityData.isProcess) {
        if (Enums.entityStateReadOnly[status]) {
          return iconBasePath + "disabled/process.png";
        } else {
          return iconBasePath + "process.png";
        }
      } else {
        if (Enums.entityStateReadOnly[status]) {
          return iconBasePath + "disabled/table.png";
        } else {
          return iconBasePath + "table.png";
        }
      }
    }

    if (entityData) {
      if (errorUrl) {
        var isErrorInTypeName =
          errorUrl &&
          errorUrl.match(
            "entity-icon/" + typeName + ".png|disabled/" + typeName + ".png"
          )
            ? true
            : false;
        if (serviceType && isErrorInTypeName) {
          var imageName = serviceType + ".png";
          return getImgPath(imageName);
        } else {
          return getDefaultImgPath();
        }
      } else if (entityData.typeName) {
        var imageName = entityData.typeName + ".png";
        return getImgPath(imageName);
      } else {
        return getDefaultImgPath();
      }
    }
  },
  /**
   * [isProcess description]
   * @param  {[type]}  options.typeName   [description]
   * @param  {[type]}  options.superTypes [description]
   * @param  {[type]}  options.entityDef  [description]
   * @return {Boolean}                    [description]
   */
  isProcess: function ({ typeName, superTypes, entityDef }) {
    if (typeName == "Process") {
      return true;
    }
    return superTypes.indexOf("Process") > -1;
  },
  /**
   * [isDeleted description]
   * @param  {[type]}  node [description]
   * @return {Boolean}      [description]
   */
  isDeleted: function (node) {
    if (node === undefined) {
      return;
    }
    return Enums.entityStateReadOnly[node.status];
  },
  isNodeToBeUpdated: function (node, filterObj) {
    var isProcessHideCheck = filterObj.isProcessHideCheck,
      isDeletedEntityHideCheck = filterObj.isDeletedEntityHideCheck;
    var returnObj = {
      isProcess: isProcessHideCheck && node.isProcess,
      isDeleted: isDeletedEntityHideCheck && node.isDeleted,
    };
    returnObj["update"] = returnObj.isProcess || returnObj.isDeleted;
    if (node.label === "Expand") {
      returnObj["update"] = true;
    }
    return returnObj;
  },
  /**
   * [getServiceType description]
   * @param  {[type]} options.typeName  [description]
   * @param  {[type]} options.entityDef [description]
   * @return {[type]}                   [description]
   */
  getServiceType: function ({ typeName, entityDef }) {
    var serviceType = null;
    if (typeName) {
      if (entityDef) {
        serviceType = entityDef.serviceType || null;
      }
    }
    return serviceType;
  },
  /**
   * [getEntityDef description]
   * @param  {[type]} options.typeName            [description]
   * @param  {[type]} options.entityDefCollection [description]
   * @return {[type]}                             [description]
   */
  getEntityDef: function ({ typeName, entityDefCollection }) {
    var entityDef = null;
    if (typeName) {
      entityDef = entityDefCollection.find(function (obj) {
        return obj.name == typeName;
      });
    }
    return entityDef;
  },
  /**
   * [getNestedSuperTypes description]
   * @param  {[type]} options.entityDef           [description]
   * @param  {[type]} options.entityDefCollection [description]
   * @return {[type]}                             [description]
   */
  getNestedSuperTypes: function ({ entityDef, entityDefCollection }) {
    var data = entityDef,
      collection = entityDefCollection,
      superTypes = new Set();

    var getData = function (data, collection) {
      if (data) {
        if (data.superTypes && data.superTypes.length) {
          data.superTypes.forEach(function (superTypeName) {
            superTypes.add(superTypeName);
            var collectionData = collection.find(function (obj) {
              obj.name === superTypeName;
            });
            if (collectionData) {
              getData(collectionData, collection);
            }
          });
        }
      }
    };
    getData(data, collection);
    return Array.from(superTypes);
  },
  generateData: function ({
    data = {},
    filterObj,
    entityDefCollection,
    g,
    guid,
    setGraphEdge,
    setGraphNode,
  }) {
    return new Promise((resolve, reject) => {
      try {
        var relations = data.relations || {},
          guidEntityMap = data.guidEntityMap || {},
          isHideFilterOn =
            filterObj.isProcessHideCheck || filterObj.isDeletedEntityHideCheck,
          newHashMap = {},
          styleObj = {
            fill: "none",
            stroke: "#ffb203",
            width: 3,
          },
          makeNodeData = (relationObj) => {
            if (relationObj) {
              if (relationObj.updatedValues) {
                return relationObj;
              }
              var nodeLabel = relationObj.displayText
                  ? relationObj.displayText
                  : " ",
                obj = Object.assign(relationObj, {
                  shape: "img",
                  updatedValues: true,
                  label: nodeLabel.trunc(18),
                  toolTipLabel: nodeLabel,
                  id: relationObj.guid,
                  isLineage: true,
                  isIncomplete: relationObj.isIncomplete,
                  entityDef: this.getEntityDef({
                    typeName: relationObj.typeName,
                    entityDefCollection,
                  }),
                });
              obj["serviceType"] = this.getServiceType(obj);
              obj["superTypes"] = this.getNestedSuperTypes({
                ...obj,
                entityDefCollection: entityDefCollection,
              });
              obj["isProcess"] = this.isProcess(obj);
              obj["isDeleted"] = this.isDeleted(obj);
              return obj;
            }
          },
          crateLineageRelationshipHashMap = function ({ relations } = {}) {
            var newHashMap = {};
            relations.forEach(function (obj) {
              if (newHashMap[obj.fromEntityId]) {
                newHashMap[obj.fromEntityId].push(obj.toEntityId);
              } else {
                newHashMap[obj.fromEntityId] = [obj.toEntityId];
              }
            });
            return newHashMap;
          },
          getStyleObjStr = function (styleObj) {
            return (
              "fill:" +
              styleObj.fill +
              ";stroke:" +
              styleObj.stroke +
              ";stroke-width:" +
              styleObj.width
            );
          },
          getNewToNodeRelationship = (toNodeGuid, filterObj) => {
            if (toNodeGuid && relationshipMap[toNodeGuid]) {
              var newRelationship = [];
              relationshipMap[toNodeGuid].forEach((guid) => {
                var nodeToBeUpdated = this.isNodeToBeUpdated(
                  makeNodeData(guidEntityMap[guid]),
                  filterObj
                );
                if (nodeToBeUpdated.update) {
                  var newRelation = getNewToNodeRelationship(guid, filterObj);
                  if (newRelation) {
                    newRelationship = newRelationship.concat(newRelation);
                  }
                } else {
                  newRelationship.push(guid);
                }
              });
              return newRelationship;
            } else {
              return null;
            }
          },
          getToNodeRelation = (toNodes, fromNodeToBeUpdated, filterObj) => {
            var toNodeRelationship = [];
            toNodes.forEach((toNodeGuid) => {
              var toNodeToBeUpdated = this.isNodeToBeUpdated(
                makeNodeData(guidEntityMap[toNodeGuid]),
                filterObj
              );
              if (toNodeToBeUpdated.update) {
                // To node need to updated
                if (pendingFromRelationship[toNodeGuid]) {
                  toNodeRelationship = toNodeRelationship.concat(
                    pendingFromRelationship[toNodeGuid]
                  );
                } else {
                  var newToNodeRelationship = getNewToNodeRelationship(
                    toNodeGuid,
                    filterObj
                  );
                  if (newToNodeRelationship) {
                    toNodeRelationship = toNodeRelationship.concat(
                      newToNodeRelationship
                    );
                  }
                }
              } else {
                //when bothe node not to be updated.
                toNodeRelationship.push(toNodeGuid);
              }
            });
            return toNodeRelationship;
          },
          setNode = (guid) => {
            if (!g._nodes[guid]) {
              var nodeData = makeNodeData(guidEntityMap[guid]);
              setGraphNode(guid, nodeData);
              return nodeData;
            } else {
              return g._nodes[guid];
            }
          },
          setEdge = function (fromNodeGuid, toNodeGuid, opt = {}) {
            setGraphEdge(fromNodeGuid, toNodeGuid, {
              arrowhead: "arrowPoint",
              curve: curveBasis,
              style: getStyleObjStr(styleObj),
              styleObj: styleObj,
              ...opt,
            });
          },
          setGraphData = function (fromEntityId, toEntityId) {
            setNode(fromEntityId);
            setNode(toEntityId);
            setEdge(fromEntityId, toEntityId);
          },
          pendingFromRelationship = {};
        if (isHideFilterOn) {
          var relationshipMap = crateLineageRelationshipHashMap(data);
          Object.keys(relationshipMap).forEach((fromNodeGuid) => {
            var toNodes = relationshipMap[fromNodeGuid],
              fromNodeToBeUpdated = this.isNodeToBeUpdated(
                makeNodeData(guidEntityMap[fromNodeGuid]),
                filterObj
              ),
              toNodeList = getToNodeRelation(
                toNodes,
                fromNodeToBeUpdated,
                filterObj
              );
            if (fromNodeToBeUpdated.update) {
              if (pendingFromRelationship[fromNodeGuid]) {
                pendingFromRelationship[fromNodeGuid] =
                  pendingFromRelationship[fromNodeGuid].concat(toNodeList);
              } else {
                pendingFromRelationship[fromNodeGuid] = toNodeList;
              }
            } else {
              toNodeList.forEach(function (toNodeGuid) {
                setGraphData(fromNodeGuid, toNodeGuid);
              });
            }
          });
        } else {
          if (relations.length) {
            relations.forEach(function (obj) {
              setGraphData(obj.fromEntityId, obj.toEntityId);
            });
          } else {
            setNode(guid);
          }
        }
        if (g._nodes[guid]) {
          if (g._nodes[guid]) {
            g._nodes[guid]["isLineage"] = false;
          }
          this.findImpactNodeAndUpdateData({
            guid,
            g,
            setEdge,
            getStyleObjStr,
          });
        }
        if (!g._nodes[guid]) {
          setNode(guid);
        }
        resolve(g);
      } catch (e) {
        reject(e);
      }
    });
  },
  findImpactNodeAndUpdateData: function ({ guid, getStyleObjStr, g, setEdge }) {
    var that = this,
      traversedMap = {},
      styleObj = {
        fill: "none",
        stroke: "#fb4200",
        width: 3,
      },
      traversed = function (toNodeList = {}, fromNodeGuid) {
        let toNodeKeyList = Object.keys(toNodeList);
        if (toNodeKeyList.length) {
          if (!traversedMap[fromNodeGuid]) {
            traversedMap[fromNodeGuid] = true;
            toNodeKeyList.forEach(function (toNodeGuid) {
              if (g._nodes[toNodeGuid]) {
                g._nodes[toNodeGuid]["isLineage"] = false;
              }
              setEdge(fromNodeGuid, toNodeGuid, {
                style: getStyleObjStr(styleObj),
                styleObj: styleObj,
              });
              traversed(g._sucs[toNodeGuid], toNodeGuid);
            });
          }
        }
      };
    traversed(g._sucs[guid], guid);
  },
};
export default DataUtils;
