# Source code for libpysal.weights.spintW

    """
    Spatial weights for spatial interaction including contiguity OD weights (ODW),
    network based weights (netW), and distance-decay based vector weights (vecW).

    """

    # ruff: noqa: N802, N803, N999

    __author__ = "Taylor Oshan  <tayoshan@gmail.com> "

    from collections import OrderedDict

    from scipy.sparse import kron

    from .distance import DistanceBand
    from .weights import WSP, W

    def ODW(Wo, Wd, transform="r", silence_warnings=True):
        """
        Constructs an o*d by o*d origin-destination style spatial weight for o*d
        flows using standard spatial weights on o origins and d destinations. Input
        spatial weights must be binary or able to be sutiably transformed to binary.

        Parameters
        ----------
        Wo          : W object for origin locations
                      o x o spatial weight object amongst o origins
        Wd          : W object for destination locations
                      d x d spatial weight object amongst d destinations
        transform   : Transformation for standardization of final OD spatial weight; default
                      is 'r' for row standardized

        Returns
        -------
        ww           : spatial contiguity W object for assocations between flows
                     o*d x o*d spatial weight object amongst o*d flows between o
                     origins and d destinations

        Examples
        --------
        >>> import libpysal
        >>> O = libpysal.weights.lat2W(2,2)
        >>> D = libpysal.weights.lat2W(2,2)
        >>> OD = libpysal.weights.ODW(O,D)
        >>> OD.weights[0]
        [0.25, 0.25, 0.25, 0.25]
        >>> OD.neighbors[0]
        [5, 6, 9, 10]
        >>> OD.full()[0][0]
        array([0.  , 0.  , 0.  , 0.  , 0.  , 0.25, 0.25, 0.  , 0.  , 0.25, 0.25,
               0.  , 0.  , 0.  , 0.  , 0.  ])
        """
        if Wo.transform != "b":
            try:
                Wo.tranform = "b"
            except:  # noqa: E722
                raise AttributeError(
                    "Wo is not binary and cannot be transformed to "
                    "binary. Wo must be binary or suitably transformed to binary."
                ) from None
        if Wd.transform != "b":
            try:
                Wd.tranform = "b"
            except:  # noqa: E722
                raise AttributeError(
                    "Wd is not binary and cannot be transformed to "
                    "binary. Wd must be binary or suitably transformed to binary."
                ) from None
        wo = Wo.sparse
        wo.eliminate_zeros()
        wd = Wd.sparse
        wd.eliminate_zeros()
        ww = kron(wo, wd, format="csr")
        ww.eliminate_zeros()
        ww = WSP(ww).to_W(silence_warnings=silence_warnings)
        ww.transform = transform
        return ww

    def netW(link_list, share="A", transform="r", **kwargs):
        """
        Create a network-contiguity based weight object based on different nodal
        relationships encoded in a network.

        Parameters
        ----------
        link_list   : list
                      of tuples where each tuple is of the form (o,d) where o is an
                      origin id and d is a destination id
        share       : string
                      denoting how to define the nodal relationship used to determine
                      neighboring edges; defualt is 'A' for any shared nodes between two
                      network edges; options include: O a shared origin node; D a shared
                      destination node; OD; a shared origin or a shared destination node;
                      C a shared node that is the destination of the first edge and the
                      origin of the second edge - i.e., a directed chain is formed moving
                      from edge one to edge two.
        transform   : Transformation for standardization of final OD spatial weight; default
                      is 'r' for row standardized
        **kwargs    : keyword arguments
                      optional arguments for :class:`pysal.weights.W`

        Returns
        -------
        net_w          : nodal contiguity W object for networkd edges or flows
                      W Object representing the binary adjacency of the network edges
                      given a definition of nodal relationshilibpysal.weights.spintW.

        Examples
        --------
        >>> import libpysal
        >>> links = [('a','b'), ('a','c'), ('a','d'), ('c','d'), ('c', 'b'), ('c','a')]
        >>> O = libpysal.weights.netW(links, share='O')
        >>> O.neighbors[('a', 'b')]
        [('a', 'c'), ('a', 'd')]
        >>> OD = libpysal.weights.netW(links, share='OD')
        >>> OD.neighbors[('a', 'b')]
        [('a', 'c'), ('a', 'd'), ('c', 'b')]
        >>> any_common = libpysal.weights.netW(links, share='A')
        >>> any_common.neighbors[('a', 'b')]
        [('a', 'c'), ('a', 'd'), ('c', 'b'), ('c', 'a')]
        """
        neighbors = {}
        neighbors = OrderedDict()
        edges = link_list
        for key in edges:
            neighbors[key] = []
            for neigh in edges:
                if key == neigh:
                    continue
                if share.upper() == "OD":
                    if key[0] == neigh[0] or key[1] == neigh[1]:
                        neighbors[key].append(neigh)
                elif share.upper() == "O":
                    if key[0] == neigh[0]:
                        neighbors[key].append(neigh)
                elif share.upper() == "D":
                    if key[1] == neigh[1]:
                        neighbors[key].append(neigh)
                elif share.upper() == "C":
                    if key[1] == neigh[0]:
                        neighbors[key].append(neigh)
                elif share.upper() == "A":
                    if (
                        key[0] == neigh[0]
                        or key[0] == neigh[1]
                        or key[1] == neigh[0]
                        or key[1] == neigh[1]
                    ):
                        neighbors[key].append(neigh)
                else:
                    raise AttributeError("Parameter 'share' must be 'O', 'D', 'OD', or 'C'")
        net_w = W(neighbors, **kwargs)
        net_w.tranform = transform
        return net_w

    def vecW(
        origin_x,
        origin_y,
        dest_x,
        dest_y,
        threshold,
        p=2,
        alpha=-1.0,
        binary=True,
        ids=None,
        build_sp=False,  # noqa: ARG001
        **kwargs,
    ):
        """
        Distance-based spatial weight for vectors that is computed using a
        4-dimensional distance between the origin x,y-coordinates and the
        destination x,y-coordinates

        Parameters
        ----------
        origin_x   : list or array
                     of vector origin x-coordinates
        origin_y   : list or array
                     of vector origin y-coordinates
        dest_x     : list or array
                     of vector destination x-coordinates
        dest_y     : list or array
                     of vector destination y-coordinates
        threshold  : float
                     distance band
        p          : float
                     Minkowski p-norm distance metric parameter:
                     1<=p<=infinity
                     2: Euclidean distance
                     1: Manhattan distance
        binary     : boolean
                     If true w_{ij}=1 if d_{i,j}<=threshold, otherwise w_{i,j}=0
                     If false wij=dij^{alpha}
        alpha      : float
                     distance decay parameter for weight (default -1.0)
                     if alpha is positive the weights will not decline with
                     distance. If binary is True, alpha is ignored
        ids         : list
                      values to use for keys of the neighbors and weights dicts
        build_sp    : boolean
                      True to build sparse distance matrix and false to build dense
                      distance matrix; significant speed gains may be obtained
                      dending on the sparsity of the of distance_matrix and
                      threshold that is applied
        **kwargs    : keyword arguments
                      optional arguments for :class:`pysal.weights.W`

        Returns
        -------
        w           : DistanceBand W object that uses 4-dimenional distances between
                      vectors origin and destination coordinates.

        Examples
        --------
        >>> import libpysal
        >>> x1 = [5,6,3]
        >>> y1 = [1,8,5]
        >>> x2 = [2,4,9]
        >>> y2 = [3,6,1]
        >>> W1 = libpysal.weights.vecW(x1, y1, x2, y2, threshold=999)
        >>> list(W1.neighbors[0])
        [1, 2]
        >>> W2 = libpysal.weights.vecW(x1, y2, x1, y2, threshold=8.5)
        >>> list(W2.neighbors[0])
        [1, 2]
        """
        data = list(zip(origin_x, origin_y, dest_x, dest_y, strict=True))
        w = DistanceBand(
            data,
            threshold=threshold,
            p=p,
            binary=binary,
            alpha=alpha,
            ids=ids,
            build_sp=False,
            **kwargs,
        )
        return w

    def mat2L(edge_matrix):
        """
        Convert a matrix denoting network connectivity (edges or flows) to a list
        denoting edges

        Parameters
        ----------
        edge_matrix   : array
                        where rows denote network edge origins, columns denote
                        network edge destinations, and non-zero entries denote the
                        existence of an edge between a given origin and destination

        Returns
        -------
         edge_list    : list
                        of tuples where each tuple is of the form (o,d) where o is an
                        origin id and d is a destination id
        """
        if len(edge_matrix.shape) != 2:
            raise AttributeError(
                "Matrix of network edges should be two dimensions"
                "with edge origins on one axis and edge destinations on the"
                "second axis with non-zero matrix entires denoting an edge"
                "between and origin and destination"
            )
        edge_list = []
        rows, cols = edge_matrix.shape
        for row in range(rows):
            for col in range(cols):
                if edge_matrix[row, col] != 0:
                    edge_list.append((row, col))
        return edge_list