# Source: port.js

    /**
     * Accessible via `require('@buggyorg/graphtools').Port`
     *
     * Methods for handling ports. A port is attached to a node and connects data sources with sinks.
     * The port format is a unique identifier for the port and its node unlike the port name.
     * @module Port */

    import curry from 'lodash/fp/curry'
    import merge from 'lodash/fp/merge'
    import _ from 'lodash'

    const OUTPUT = 'output'
    const INPUT = 'input'

    /**
     * Checks whether a string represents a port notation. A port notation is a string that
     * contains the information about the node and the port separated by an `@`, e.g. 'nodeA@portB'
     * @params {string} port The string to check
     * @returns {boolean} True if the string represents a port notation, false otherwise.
     */
    export function isPortNotation (port) {
      return typeof (port) === 'string' && port.indexOf('@') !== -1
    }

    function parsePortNotation (port) {
      var split = port.split('@')
      var res = {}
      res.node = split[0]
      if (split[1] === '') {
        throw new Error('Invalid port notation. Port notation does not contain a port. Parsed port: ' + port)
      } else {
        res.port = split[1]
      }
      return res
    }

    /**
     * Returns a normalized port object in the form `{node: <node-id>, port: <port-name>}`
     * @param port The port object in any digestable form.
     * @returns {Port} A port object.
     */
    export function normalize (port) {
      if (isPortNotation(port)) {
        return assureType(parsePortNotation(port))
      } else {
        if (isPortNotation(port.port)) {
          return assureType(merge(port, {port: parsePortNotation(port.port).port}))
        }
        return assureType(port)
      }
    }

    function assureType (port) {
      if (!port.type) {
        return _.merge({type: 'generic'}, port)
      }
      return port
    }

    /**
     * Returns the node stored in the port
     * @param {Port} port The port in any form.
     * @returns {NodeID} The node of the port.
     */
    export function node (port) {
      return normalize(port).node
    }

    /**
     * Returns the port name stored for the port.
     * @param {Port} port The port in any form.
     * @returns {string} The name of the port.
     */
    export function portName (port) {
      return normalize(port).port
    }

    /**
     * Returns the data type of the port.
     * @param {Port} port The port
     * @returns The type of the port.
     */
    export function type (port) {
      return normalize(port).type
    }

    /**
     * Sets the type of a port and returns a new port with this updated port.
     * @param type The type to set.
     * @param {Port} port The port
     * @returns The updated port.
     */
    export function setType (type, port) {
      return merge(port, {type})
    }

    /**
     * Returns the kind of the port. Either output or input.
     * @param {Port} port The port
     * @returns The kind of the port. It is either 'input' or 'output'.
     */
    export function kind (port) {
      return normalize(port).kind
    }

    /**
     * Returns whether the given port is an output port or not
     * @param {Port} port The port to check
     * @returns {boolean} True if the port is an output port, false otherwise.
     */
    export function isOutputPort (port) {
      return port.kind === OUTPUT
    }

    /**
     * Returns whether the given port is an input port or not
     * @param {Port} port The port to check
     * @returns {boolean} True if the port is an input port, false otherwise.
     */
    export function isInputPort (port) {
      return port.kind === INPUT
    }

    /**
     * Returns whether the given object is a port object or not.
     * @params any Any value.
     * @returns {boolean} True if the value is a port, false otherwise.
     */
    export function isPort (any) {
      return (typeof (any) === 'object' && typeof (any.node) === 'string' && typeof (any.port) === 'string') ||
        typeof (any) === 'string' && isPortNotation(any)
    }

    /**
     * Returns whether the given port is valid or not.
     * @param port The object to test.
     * @returns {boolean} True if the value is a valid port, false otherwise.
     */
    export function isValid (port) {
      return typeof (port) === 'object' && port.port && (port.kind === INPUT || port.kind === OUTPUT) && port.type
    }

    /**
     * Asserts that the given port is valid.
     * @param port The object to test.
     * @throws {Error} Error if the object is not a valid port.
     */
    export function assertValid (port) {
      if (typeof (port) !== 'object') {
        throw new Error('Port is not an object, but is ' + port)
      }
      if (!port.port) {
        throw new Error('Port does not have a `port` prop')
      }
      if (!port.type) {
        throw new Error('Port does not have a `type` prop')
      }
      if (port.kind !== INPUT && port.kind !== OUTPUT) {
        throw new Error('Port `kind` prop should be "input" or "output", but was ' + port.kind)
      }
    }

    /**
     * Returns a string representation of the port
     * @params {Port} port The port
     * @returns {string} A string representation of the port.
     */
    export function toString (port) {
      return node(port) + '@' + portName(port)
    }

    /**
     * @function
     * @name equal
     * @description Determines if two ports are equal
     * @param {Port} port1 One of the ports.
     * @param {Port} port2 The other port..
     * @returns {boolean} True if the ports are equal, false otherwise.
     */
    export const equal = curry((port1, port2) => {
      return node(port1) === node(port2) && isomorph(port1, port2)
    })

    export const isomorph = curry((port1, port2) => {
      return portName(port1) === portName(port2) && (type(port1) === type(port2))
    })

    /**
     * @function
     * @name create
     * @description Create a new port object.
     * @param {Node|String} node A node object or the id of a valid node.
     * @param {String} port The name of the port
     * @param {String} kind 'input' or 'output'
     * @returns {Port} A valid port object.
     */
    export const create = curry((node, port, kind, type = 'generic') => {
      if (typeof (node) === 'object') {
        return normalize({node: node.id, port, kind, type})
      } else {
        return normalize({node, port, kind, type})
      }
    })

    /**
     * @function
     * @name port
     * @description Create a proto port object that can be used as a location. It is no
     * valid port.
     * @param {Node|String} node A node object or the id of a valid node.
     * @param {String} port The name of the port
     * @returns {Port} A valid port object.
     */
    export const port = curry((node, port) => {
      if (typeof (node) === 'object') {
        return normalize({node: node.id, port})
      } else {
        return normalize({node, port})
      }
    })