# Source: edge.js

    /**
     * Accessible via `require('@buggyorg/graphtools').Edge`
     * @module Edge */

    import curry from 'lodash/fp/curry'
    import merge from 'lodash/fp/merge'
    import _ from 'lodash'
    import * as Port from './port'
    import * as Node from './node'

    function normalizeStructure (edge) {
      if (!_.has(edge, 'from') || !_.has(edge, 'to')) {
        throw new Error('The edge format is not valid. You need to have a from and to value in.\n\n' + JSON.stringify(edge, null, 2) + '\n')
      }
      var layer = edge.layer || 'dataflow'
      if (layer !== 'dataflow') {
        var newEdge = _.clone(edge)
        if (Node.isValid(edge.from)) {
          newEdge.from = Node.id(edge.from)
        }
        if (Node.isValid(edge.to)) {
          newEdge.to = Node.id(edge.to)
        }
        return newEdge
      }
      if ((typeof (edge.from) === 'string' && edge.from[0] === '/') ||
        (typeof (edge.to) === 'string' && edge.to[0] === '/')) {
        return merge(edge, {query: true,
            from: (Port.isPort(edge.from)) ? Port.normalize(edge.from) : edge.from,
            to: (Port.isPort(edge.to)) ? Port.normalize(edge.to) : edge.to
          })
      } if (edge.outPort && edge.inPort) {
        return _.merge({}, _.omit(edge, ['outPort', 'inPort']),
          {layer, from: Port.normalize({node: edge.from, port: edge.outPort}), to: Port.normalize({node: edge.to, port: edge.inPort})})
      } else if (!edge.outPort && !edge.inPort && Port.isPort(edge.from) && Port.isPort(edge.to)) {
        return { from: Port.normalize(edge.from), to: Port.normalize(edge.to), layer }
      } else {
        throw new Error('Malformed edge. Cannot translate format into standard format.\nEdge: ' + JSON.stringify(edge))
      }
    }

    /**
     * Normalizes the edge into the standard format
     *
     * ```
     *  {from: '<port>', to: '<port>'}
     * ```
     *
     * It accepts the following short forms:
     *
     * ```
     *  {from: '<node>@<port>', to: '<node>@<port>'}
     *  {from: '@<port>', to: '<node>@<port>'}
     *  {from: '<node>@<port>', to: '@<port>'}
     *  {from: '@<port>', to: '@<port>'}
     *  {from: '<node>', to: '<node>', outPort: '<port-name>', inPort: '<port-name>'}
     * ```
     *
     * The format must be consistent, you cannot have a mixture for `from` and `to`.
     * It is not possible to always add those normalized edges into the graph. They must contain
     * valid IDs of the graph. Use `Graph.normalize` for this.
     *
     * @param {Edge} edge The edge object that should be normalized.
     * @returns {Edge} The normalized form of the edge.
     * @throws {Error} An error is thrown if the edge is not in a consistent format.
     */
    export function normalize (edge) {
      var newEdge = normalizeStructure(edge)
      if (typeof (newEdge.from) === 'object' && newEdge.from.node.length === 0) {
        newEdge.innerCompoundOutput = true
      }
      if (typeof (newEdge.to) === 'object' && newEdge.to.node.length === 0) {
        newEdge.innerCompoundInput = true
      }
      return newEdge
    }

    export function isEdgeToParent (edge) {
      return edge.innerCompoundInput
    }

    export function isInnerEdge (edge) {
      return !edge.innerCompoundInput && !edge.innerCompoundOutput
    }

    export function isEdgeFromParent (edge) {
      return edge.innerCompoundOutput
    }

    /**
     * @function
     * @name equal
     * @description Checks whether two normalized edges are equal.
     * @param {Edge} edge1 The first edge for the comparison.
     * @param {Edge} edge2 The second edge for the comparison.
     * @returns {boolean} True if the edges are equal (i.e. they connect the same ports), false otherwise.
     */
    export const equal = curry((edge1, edge2) => {
      if (edge1.layer === 'dataflow') {
        return Port.node(edge1.from) === Port.node(edge2.from) && Port.node(edge1.to) === Port.node(edge2.to) &&
          Port.portName(edge1.from) === Port.portName(edge2.from) && Port.portName(edge1.to) === Port.portName(edge2.to) &&
          edge1.layer === edge2.layer
      } else {
        return edge1.from === edge2.from && edge1.to === edge2.to && edge1.layer === edge2.layer
      }
    })

    /**
     * Returns a copy of the edge where the path is prefixed with the specified path. [Does nothing currently... can probably be removed. Is used in ./compound.js]
     * @param {Edge} edge The edge that will be prefixed
     * @param {CompoundPath} path The compound path that prefixes the edge paths.
     * @returns {Edge} A new edge that has the prefixed paths.
     */
    export function setPath (edge, path) {
      return edge
    }

    /**
     * Gets the type of an edge. Note that not every edge must have a type and that the type is not stored inside the json document.
     * If you use the graph functions to iterate over edges you always get the type (if available) with the edge.
     * @example <caption>Get an edge via a graph method and then get its edge.</caption>
     * var edge = Graph.incident(port, graph)
     * var type = Edge.type(edge)
     * @example <caption>It will yield undefined if you simply access an edge in the json document, do not do that.</caption>
     * var edge = graph.edges[0]
     * //this will return undefined
     * var type = Edge.type(edge) // = undefined
     * @param {Edge} edge The edge to use.
     * @returns {Type|undefined} Either a real type, a typename or `undefined`. Some edges do not have type information. Usually non-dataflow edges like
     * recursion indicators. Those will yield `undefined`.
     */
    export function type (edge) {
      return edge.type
    }

    /**
     * Explicitly sets the type of the edge. The type of an edge is determined by the connecting ports.
     * @param {Type} type The type of the edge.
     * @param {Edge} edge The edge that should get the type.
     * @returns {Edge} A new edge that has a field for the type.
     */
    export function setType (type, edge) {
      if (isBetweenPorts(edge)) {
        return merge(edge, {type, from: Port.setType(type, edge.from), to: Port.setType(type, edge.to)})
      } else if (!isBetweenNodes(edge)) {
        throw new Error('[Edge.setType] Cannot handle mixed edges (from port to node or from node into port)')
      }
      return merge({type}, edge)
    }

    /**
     * Checks whether an object is a valid edge.
     * @param {Object} edge The object to test.
     * @returns {Boolean} True if the object is an edge, false otherwise.
     */
    export function isValid (edge) {
      return typeof (edge) === 'object' && edge.from && edge.to
    }

    export function isBetweenPorts (edge) {
      return typeof (edge) === 'object' &&
        Port.isValid(Object.assign({type: '-', kind: 'input'}, edge.from)) &&
        Port.isValid(Object.assign({type: '-', kind: 'output'}, edge.to))
    }

    export function isBetweenNodes (edge) {
      return isValid(edge) && !isBetweenPorts(edge)
    }