module BusWireRoute

open CommonTypes
open BlockHelpers
open DrawModelType.SymbolT
open DrawModelType.BusWireT
open BusWire
open BusWireUpdateHelpers
open BusWireRoutingHelpers


open Optics
open Operators



(* 
NOTE:   For ease of understanding, algorithm, variable names and documentation of code below are all explained 
        in the simple case of no rotated symbols (ie wire.InitialOrientation = Horizontal).

        However, the code implemented supports the rotated case as well.

Implemented the following Smart Routing Algorithm:

    1)  Check if initial autorouted wire has any intersections with symbols. 
        If yes, calculate the bounding boxes of all the intersected symbols.
    2)  Attempt to shift the vertical seg of the 7 seg wire to wireSeparationFromSymbol amount left of the left most
        bound of the intersected symbols. 
        If there are still intersections, try shifting to the right most bound + wireSeparationFromSymbol.
    3)  If there are still intersections, recursively try to shift the horizontal seg of the 7 seg 
        or 9 seg wire to either the top or bottom most bound of the intersected symbols. 
        If both shifted wires still result in an intersection, compute the vertical distances between 
        the start/end pos of the wire and the top/bottom bound of the intersected symbols. 
        Using the 4 vertical distances computed, decide whether to try shifting the wire up or down 
        depending on which results in a wire with shorter vertical distance.
        
        A max recursion depth is defined for step 3 so that Issie will not break when there are physically 
        no possible routes that will not intersect any symbol (eg when dragging a symbol around such that 
        the dragged symbol is within another symbol) or when there are special corner cases that have not 
        been implemented yet (eg symbol A is in top left quadrant with input port facing up, connected to
        symbol B in bottom right quadrant with output port facing down, with other symbols in between the
        2 symbols).
*)

//*************************************************************************************************************
//                                 See SmartHelpers for Constants submodule
//**************************************************************************************************************

open BusWireRoutingHelpers.Constants



/// Checks if a wire intersects any symbol within +/- minWireSeparation
/// Returns list of bounding boxes of symbols intersected by wire.
let findWireSymbolIntersections (model: Model) (wire: Wire) : BoundingBox list =
    let allSymbolsIntersected =
        model.Symbol.Symbols
        |> Map.values
        |> Seq.toList
        |> List.filter (fun s -> s.Annotation = None)
        |> List.map (fun s -> (s.Component.Type, Symbol.getSymbolBoundingBox s))


    let wireVertices =
        segmentsToIssieVertices wire.Segments wire
        |> List.map (fun (x, y, _) -> { X = x; Y = y })

    let indexes = List.init ((List.length wireVertices)-2) (fun i -> i+1)

    let segVertices = List.pairwise wireVertices.[1 .. wireVertices.Length - 2] |> List.zip indexes // do not consider the nubs

    let inputCompId = model.Symbol.Ports.[string wire.InputPort].HostId
    let outputCompId = model.Symbol.Ports.[string wire.OutputPort].HostId

    let componentIsMux (comp:Component) =
        match comp.Type with
        | Mux2 | Mux4 | Mux8 | Demux2 | Demux4 | Demux8 -> true
        | _ -> false

    // this was added to fix MUX SEL port wire rooting bug, it is irrelevant in other cases
    let inputIsSelect =
        let inputSymbol = model.Symbol.Symbols.[ComponentId inputCompId]
        let inputCompInPorts = inputSymbol.Component.InputPorts
        
        componentIsMux inputSymbol.Component && (inputCompInPorts.[List.length inputCompInPorts - 1].Id = string wire.InputPort)

    let inputCompRotation =
        model.Symbol.Symbols.[ComponentId inputCompId].STransform.Rotation

    let outputCompRotation =
        model.Symbol.Symbols.[ComponentId outputCompId].STransform.Rotation

    let isConnectedToSelf = inputCompId = outputCompId


    let boxesIntersectedBySegment (lastSeg:bool) startPos endPos =
        allSymbolsIntersected
        |> List.map (fun (compType, boundingBox) ->
            (
                compType,
                {
                    W = boundingBox.W + minWireSeparation * 2.
                    H = boundingBox.H + minWireSeparation * 2.
                    TopLeft =
                    boundingBox.TopLeft
                    |> updatePos Left_ minWireSeparation
                    |> updatePos Up_ minWireSeparation
                }
            ))
        |> List.filter (fun (compType, boundingBox) ->
            // don't check if the final segments of a wire that connects to a MUX SEL port intersect with the MUX bounding box
            match compType, lastSeg with
            | Mux2, true | Mux4, true | Mux8, true | Demux2, true | Demux4, true | Demux8, true -> false
            | _, _ ->
                 match segmentIntersectsBoundingBox boundingBox startPos endPos with // do not consider the symbols that the wire is connected to
                 | Some _ -> true // segment intersects bounding box
                 | None -> false // no intersection
        )
        |> List.map (fun (compType, boundingBox) -> boundingBox)


    segVertices
    |> List.collect (fun (i, (startPos, endPos)) -> boxesIntersectedBySegment (i > List.length segVertices - 2 && inputIsSelect) startPos endPos)
    |> List.distinct


//------------------------------------------------------------------------//
//--------------------------Shifting Vertical Segment---------------------//
//------------------------------------------------------------------------//

let changeSegment (segIndex: int) (newLength: float) (segments: Segment list) =
    List.updateAt segIndex { segments[segIndex] with Length = newLength } segments

/// Try shifting vertical seg to either - wireSeparationFromSymbol or + wireSeparationFromSymbol of intersected symbols.
/// Returns Ok or Error depending on whether route is found. Error contains attempted new wires.
let tryShiftVerticalSeg (model: Model) (intersectedBoxes: BoundingBox list) (wire: Wire) : Result<Wire, Wire*Wire> =
    let wireVertices =
        segmentsToIssieVertices wire.Segments wire
        |> List.map (fun (x, y, _) -> { X = x; Y = y })

    let shiftVerticalSeg amountToShift =
        let newSegments =
            wire.Segments
            |> List.updateAt 2 { wire.Segments[2] with Length = wire.Segments[2].Length + amountToShift }
            |> List.updateAt 4 { wire.Segments[4] with Length = wire.Segments[4].Length - amountToShift }

        { wire with Segments = newSegments }

    let tryShiftWireVert (dir: DirectionToMove) =
        let boundBox =
            intersectedBoxes
            |> List.sortWith (fun box1 box2 ->
                let box1 = swapBB box1 wire.InitialOrientation
                let box2 = swapBB box2 wire.InitialOrientation

                match dir with
                | Left_ -> compare (box1.TopLeft.X) (box2.TopLeft.X)
                | Right_ -> compare (box2.TopLeft.X + box2.W) (box1.TopLeft.X + box1.W)
                | _ -> failwith "Invalid direction to shift wire")
            |> List.head


        let viablePos =
            match dir, wire.InitialOrientation with
            | Left_, Horizontal ->
                let initialAttemptPos = updatePos Left_ smallOffset boundBox.TopLeft
                initialAttemptPos
            | Right_, Horizontal ->
                let initialAttemptPos =
                    updatePos Right_ (boundBox.W + smallOffset) boundBox.TopLeft
                initialAttemptPos
            | Left_, Vertical ->
                let initialAttemptPos = updatePos Up_ smallOffset boundBox.TopLeft
                initialAttemptPos
            | Right_, Vertical ->
                let initialAttemptPos =
                    updatePos Down_ (boundBox.H + smallOffset) boundBox.TopLeft
                initialAttemptPos
            | _ -> failwith "Invalid direction to shift wire"

        let amountToShift =
            (swapXY viablePos wire.InitialOrientation).X
            - (swapXY wireVertices[4] wire.InitialOrientation).X

        shiftVerticalSeg amountToShift

    let tryShiftLeftWire = tryShiftWireVert Left_
    let tryShiftRightWire = tryShiftWireVert Right_

    let leftShiftedWireIntersections =
        findWireSymbolIntersections model tryShiftLeftWire

    let rightShiftedWireIntersections =
        findWireSymbolIntersections model tryShiftRightWire

    // Check which newly generated wire has no intersections, return that
    //match leftShiftedWireIntersections, rightShiftedWireIntersections with
    //| [], _ -> Some tryShiftLeftWire
    //| _, [] -> Some tryShiftRightWire
    //| _, _ ->  None
    match leftShiftedWireIntersections, rightShiftedWireIntersections with
    | [], _ -> Ok tryShiftLeftWire
    | _, [] -> Ok tryShiftRightWire
    | _, _ -> Error (tryShiftLeftWire, tryShiftRightWire)

/// Wrapper to implement the old shift vertical algorithm, for testing.
let tryShiftVerticalSegOld (model: Model) (intersectedBoxes: BoundingBox list) (wire: Wire) : Wire option =
    match tryShiftVerticalSeg model intersectedBoxes wire with
    | Ok w -> Some w
    | Error (w1, w2) -> None

//------------------------------------------------------------------------//
//-------------------------Shifting Horizontal Segment--------------------//
//------------------------------------------------------------------------//
type VertDistFromBoundingBox =
    | Above of float // Vertical distance between pos and a bounding box above
    | Below of float // Vertical distance between pos and a bounding box below


//***************************************************************************************************************//
//**************************************** NEW implementation ****************************************************
//***************************************************************************************************************//

// return Some max distance above or below, if one exists, or None
let tryMaxDistance (distances: VertDistFromBoundingBox option list) =
    match distances with
    | [] -> None
    | _ ->
        List.maxBy
            (function
            | Some(Above d)
            | Some(Below d) -> d
            | None -> -infinity)
            distances

/// returns the maximum vertical distance of pos from intersectedBoxes as a VertDistFromBoundingBox or None if there are no intersections
let maxVertDistanceFromBox
    (intersectedBoxes: BoundingBox list)
    (wireOrientation: Orientation)
    (pos: XYPos)
    : VertDistFromBoundingBox option =

    let isCloseToBoxHoriz (box: BoundingBox) (pos: XYPos) =
        inMiddleOrEndOf box.TopLeft.X pos.X (box.TopLeft.X + box.W)

    let getVertDistanceToBox (pos: XYPos) (box: BoundingBox) : VertDistFromBoundingBox option list =
        (swapXY pos wireOrientation, swapBB box wireOrientation)
        ||> (fun pos box ->
            if isCloseToBoxHoriz box pos then
                if pos.Y > box.TopLeft.Y then
                    [ pos.Y - box.TopLeft.Y |> Above |> Some ]
                else
                    [ box.TopLeft.Y - pos.Y + box.H |> Below |> Some ]
            else
                [])

    intersectedBoxes
    |> List.collect (fun box -> getVertDistanceToBox pos box)
    |> tryMaxDistance



/// Recursively shift horizontal seg up/down until no symbol intersections.
/// Limit in recursion depth defined by argument callsLeft given to initial function call.
/// Limit needed to prevent Issie from breaking when there are physically
/// no possible routes that achieve 0 intersections.
/// Returns Error route if no route found
let rec tryShiftHorizontalSeg
    (callsLeft: int)
    (model: Model)
    (intersectedBoxes: BoundingBox list)
    (wire: Wire)
    : Result<Wire, Wire> =
    match callsLeft with
    | 0 -> Error wire
    | n ->
        let tryShiftHorizontalSeg = tryShiftHorizontalSeg (n - 1)

        let currentStartPos, currentEndPos = getStartAndEndWirePos wire

        let shiftWireHorizontally firstVerticalSegLength secondVerticalSegLength =

            let moveHorizSegment vertSegIndex =
                changeSegment (vertSegIndex - 1) firstVerticalSegLength
                >> changeSegment (vertSegIndex + 1) secondVerticalSegLength

            let newSegments =
                match wire.Segments.Length with
                | 5
                | 6 -> wire.Segments |> moveHorizSegment 2

                | 7 ->
                    // Change into a 5 segment wire
                    wire.Segments[..4]
                    |> moveHorizSegment 2
                    |> changeSegment 2 (wire.Segments.[2].Length + wire.Segments.[4].Length)
                    |> List.updateAt 4 { wire.Segments.[6] with Index = 4 }

                | 9 ->
                    // Change segments index 1,3,5,7. Leave rest as is
                    wire.Segments |> changeSegment 1 0. |> moveHorizSegment 4 |> changeSegment 7 0.

                | _ -> wire.Segments

            { wire with Segments = newSegments }

        // directionToMove must be UP_ or DOWN_
        let shiftedWire (direction: DirectionToMove) =
            let orientation = wire.InitialOrientation

            let getXOrY =
                fun (pos: XYPos) ->
                    match orientation with
                    | Horizontal -> pos.X
                    | Vertical -> pos.Y

            let getOppositeXOrY =
                fun (pos: XYPos) ->
                    match orientation with
                    | Horizontal -> pos.Y
                    | Vertical -> pos.X

            let getWOrH =
                fun (box: BoundingBox) ->
                    match orientation with
                    | Horizontal -> box.W
                    | Vertical -> box.H

            let getOppositeWOrH =
                fun (box: BoundingBox) ->
                    match orientation with
                    | Horizontal -> box.H
                    | Vertical -> box.W

            let offsetOfBox, otherDir =
                match direction with
                | Up_ -> (fun _ -> 0.), Left_
                | Down_ -> (fun box -> getOppositeWOrH box), Right_
                | _ -> failwithf "What? Can't happen"

            let boundBox =
                intersectedBoxes
                |> match direction with
                   | Down_ -> List.maxBy (fun box -> getOppositeXOrY box.TopLeft + getOppositeWOrH box)
                   | Up_ -> List.minBy (fun box -> getOppositeXOrY box.TopLeft)
                   | _ -> failwithf "What? Can't happen"

            let bound =
                let offset = smallOffset + offsetOfBox boundBox

                let otherOrientation =
                    match orientation with
                    | Horizontal -> direction
                    | Vertical -> otherDir

                let initialAttemptPos = updatePos otherOrientation offset boundBox.TopLeft
                initialAttemptPos |> getOppositeXOrY

            let firstVerticalSegLength, secondVerticalSegLength =
                bound - getOppositeXOrY currentStartPos, getOppositeXOrY currentEndPos - bound


            shiftWireHorizontally firstVerticalSegLength secondVerticalSegLength

        let goodWire dir =
            let shiftedWire = shiftedWire dir

            match findWireSymbolIntersections model shiftedWire with
            | [] -> Ok shiftedWire
            | intersectedBoxes -> Error(intersectedBoxes, shiftedWire)

        // If newly generated wire has no intersections, return that
        // Otherwise, decide to shift up or down based on which is closer
        match goodWire Up_, goodWire Down_ with
        | Ok upWire, Ok downWire ->
            if getWireLength upWire < getWireLength downWire then
                Ok upWire
            else
                Ok downWire
        | Ok upWire, _ -> Ok upWire
        | _, Ok downWire -> Ok downWire
        | Error(upIntersections, upShiftedWire), Error(downIntersections, downShiftedWire) ->
            [ currentStartPos; currentEndPos ]
            |> List.map (maxVertDistanceFromBox intersectedBoxes wire.InitialOrientation)
            |> tryMaxDistance
            |> (function
            | None
            | Some(Above _) -> tryShiftHorizontalSeg model downIntersections downShiftedWire
            | Some(Below _) -> tryShiftHorizontalSeg model upIntersections upShiftedWire)

/// Wrapper function for old implementation for testing.
let tryShiftHorizontalSegOld
    (callsLeft: int)
    (model: Model)
    (intersectedBoxes: BoundingBox list)
    (wire: Wire)
    : Wire option =
    match tryShiftHorizontalSeg callsLeft model intersectedBoxes wire with
    | Ok w -> Some w
    | Error _ -> None

//------------------------------------------------------------------------//
//-----------------------------Snapping to Net----------------------------//
//------------------------------------------------------------------------//

let getWireVertices (wire: Wire) =
    segmentsToIssieVertices wire.Segments wire
    |> List.map (fun (x, y, _) -> { X = x; Y = y })

let copySegments (wire: Wire) (refWire: Wire) (numOfSegsToCopy: int) : Segment list =
    [ 0 .. numOfSegsToCopy - 1 ]
    |> List.map (fun i -> { wire.Segments[i] with Length = refWire.Segments[i].Length })

let generateEndSegments (startIndex: int) (numOfSegs: int) (wire: Wire) : Segment list =
    [ startIndex .. startIndex + numOfSegs - 1 ]
    |> List.map (fun i ->
        { wire.Segments[i % 2] with
            Length = 0.
            Index = i })
    |> List.updateAt (numOfSegs - 1) { wire.Segments.[numOfSegs - 1] with Length = nubLength }

/// Finds the first reference wire in a net and keeps the same segment lengths
/// as much as possible based on a heuristic.
/// Snap to net only implemented for one orientation
let snapToNet (model: Model) (wireToRoute: Wire) : Wire =

    let inputCompId =
        ComponentId model.Symbol.Ports[string wireToRoute.InputPort].HostId

    let outputCompId =
        ComponentId model.Symbol.Ports[string wireToRoute.OutputPort].HostId

    let isRotated =
        model.Symbol.Symbols[inputCompId].STransform.Rotation = Degree90
        || model.Symbol.Symbols[inputCompId].STransform.Rotation = Degree270
        || model.Symbol.Symbols[outputCompId].STransform.Rotation = Degree90
        || model.Symbol.Symbols[outputCompId].STransform.Rotation = Degree270

    let wireToRouteStartPos, wireToRouteEndPos = getStartAndEndWirePos wireToRoute

    match
        wireToRoute.Segments.Length,
        isRotated,
        wireToRoute.InitialOrientation,
        wireToRouteStartPos.X > wireToRouteEndPos.X,
        isWireInNet model wireToRoute
    with
    | n, _, _, _, _ when n <> 5 && n <> 7 -> wireToRoute // If wire is not 5 or 7 seg, return original wire
    | _, true, _, _, _ -> wireToRoute // If either input or output component is rotated, return original wire
    | _, _, orientation, _, _ when orientation <> Horizontal -> wireToRoute // If wire is not horizontal, return original wire
    | _, _, _, true, _ -> wireToRoute // If input is on right side of output, return original wire
    | _, _, _, _, None -> wireToRoute // If wire is not in net, return original wire
    | _, _, _, _, Some(_, netlist) ->
        // Take first wire in netlist that is not wireToRoute as reference wire for snapping
        let refWire = netlist |> List.find (fun (_, w) -> w.WId <> wireToRoute.WId) |> snd

        let refWireVertices = getWireVertices refWire

        let _, refEndPos = getStartAndEndWirePos refWire

        let firstBendPos = refWireVertices[3]
        let horizontalSegLength = refWire.Segments[2].Length

        let isHorizontalSegTooShort =
            (wireToRouteEndPos.X - wireToRouteStartPos.X) < horizontalSegLength / 2.

        let numOfSegsToCopy =
            let simpleCase =
                match wireToRouteEndPos.X < firstBendPos.X, isHorizontalSegTooShort with
                | true, true -> 1
                | true, false -> 2
                | false, _ -> 3

            match refWire.Segments.Length with
            | 5 ->
                match firstBendPos.Y < refEndPos.Y, firstBendPos.Y > wireToRouteEndPos.Y with
                | (true, true)
                | (false, false) -> if wireToRouteEndPos.X < firstBendPos.X then 2 else 3
                | _ -> simpleCase
            | 7 -> simpleCase
            | _ -> 0 // Not implemented for ref wires that are not 5 or 7 seg

        let newSegments =
            match numOfSegsToCopy with
            | 3 ->
                copySegments wireToRoute refWire 3
                @ [ { wireToRoute.Segments[3] with Length = wireToRouteEndPos.Y - firstBendPos.Y } ]
                  @ [ { wireToRoute.Segments[4] with Length = wireToRouteEndPos.X - firstBendPos.X } ]
                    @ generateEndSegments 5 2 wireToRoute
            | 2 ->
                copySegments wireToRoute refWire 2
                @ [ { wireToRoute.Segments[2] with Length = wireToRouteEndPos.X - wireToRouteStartPos.X - nubLength } ]
                  @ [ { wireToRoute.Segments[3] with Length = wireToRouteEndPos.Y - firstBendPos.Y } ]
                    @ generateEndSegments 4 3 wireToRoute
            | 1 ->
                copySegments wireToRoute refWire 1
                @ [ { wireToRoute.Segments[1] with Length = wireToRouteEndPos.Y - wireToRouteStartPos.Y } ]
                  @ [ { wireToRoute.Segments[2] with Length = wireToRouteEndPos.X - wireToRouteStartPos.X - nubLength } ]
                    @ [ { wireToRoute.Segments[3] with Length = 0. } ]
                      @ generateEndSegments 4 3 wireToRoute
            | 0 -> wireToRoute.Segments // Not implemented for ref wires that are not 5 or 7 seg
            | _ -> failwithf "Shouldn't happen"

        { wireToRoute with Segments = newSegments }


/// top-level function which replaces autoupdate and implements a smarter version of same
/// it is called every time a new wire is created, so is easily tested.
let smartAutoroute (model: Model) (wire: Wire) : Wire =
     
    let initialWire = (autoroute model wire)
    
    // Snapping to Net only if model.SnapToNet toggled to be true
    let snappedToNetWire =
        match model.SnapToNet with
        | _ -> initialWire // do not snap
        //| true -> snapToNet model initialWire

    let intersectedBoxes = findWireSymbolIntersections model snappedToNetWire 

    match intersectedBoxes.Length with
    | 0 -> snappedToNetWire
    | _ ->
        let resultOld =
            tryShiftVerticalSegOld model intersectedBoxes snappedToNetWire
            |> Option.orElse (
                tryShiftHorizontalSegOld maxCallsToShiftHorizontalSeg model intersectedBoxes snappedToNetWire
            )
            |> Option.defaultValue snappedToNetWire
        let resultNew =
            match tryShiftVerticalSeg model intersectedBoxes snappedToNetWire with
            | Ok wire -> wire
            | Error (lWire, rWire) -> 
                let lIntersect = findWireSymbolIntersections model lWire
                let rIntersect = findWireSymbolIntersections model rWire
                tryShiftHorizontalSegOld maxCallsToShiftHorizontalSeg model intersectedBoxes snappedToNetWire
                |> Option.orElse (tryShiftHorizontalSegOld maxCallsToShiftHorizontalSeg model lIntersect lWire)
                |> Option.orElse (tryShiftHorizontalSegOld maxCallsToShiftHorizontalSeg model rIntersect rWire)
                |> Option.defaultValue snappedToNetWire
                //match tryShiftHorizontalSeg maxCallsToShiftHorizontalSeg model intersectedBoxes snappedToNetWire with
                //| Ok hWire -> hWire
                //| Error hWire ->
                //    let hIntersect = findWireSymbolIntersections model hWire
                //    tryShiftHorizontalSegOld maxCallsToShiftHorizontalSeg model lIntersect lWire
                //    |> Option.orElse (tryShiftHorizontalSegOld maxCallsToShiftHorizontalSeg model rIntersect rWire)
                //    |> Option.orElse (tryShiftVerticalSegOld model hIntersect hWire)
                //    |> Option.defaultValue snappedToNetWire
        resultNew
        //match tryShiftHorizontalSeg maxCallsToShiftHorizontalSeg model intersectedBoxes snappedToNetWire with
        //| Ok w -> w
        //| Error w -> w
        //    printf "failed"
        //    snappedToNetWire
        //match tryShiftVerticalSeg model intersectedBoxes snappedToNetWire with
        //| Ok wire -> wire
        //| Error (lWire, rWire) -> rWire



//-----------------------------------------------------------------------------------------------------------//
//---------------------------------------------Top-level Wire Routing Functions------------------------------//
//-----------------------------------------------------------------------------------------------------------//

/// Returns a single re-routed wire from the given model.
/// First attempts partial autorouting, and defaults to full autorouting if this is not possible.
/// Reverse indicates if the wire should be processed in reverse, 
/// used when an input port (end of wire) is moved.
let updateWire (model : Model) (wire : Wire) (reverse : bool) =
    let newPort = 
        match reverse with
        | true -> Symbol.getInputPortLocation None model.Symbol wire.InputPort
        | false -> Symbol.getOutputPortLocation None model.Symbol wire.OutputPort
    if reverse then
        partialAutoroute model (reverseWire wire) newPort true
        |> Option.map reverseWire
    else 
        partialAutoroute model wire newPort false
    |> Option.defaultWith (fun () ->
        smartAutoroute model wire)

/// Re-routes the wires in the model based on a list of components that have been altered.
/// If the wire input and output ports are both in the list of moved components, 
/// it does not re-route wire but instead translates it.
/// Keeps manual wires manual (up to a point).
/// Otherwise it will auto-route wires connected to components that have moved
let updateWires (model : Model) (compIdList : ComponentId list) (diff : XYPos) =

    let wires = filterWiresByCompMoved model compIdList

    let newWires =
        model.Wires
        |> Map.toList
        |> List.map (fun (cId, wire) -> 
            if List.contains cId wires.Both //Translate wires that are connected to moving components on both sides
            then (cId, moveWire wire diff)
            elif List.contains cId wires.Inputs //Only route wires connected to ports that moved for efficiency
            then (cId, updateWire model wire true)
            elif List.contains cId wires.Outputs
            then (cId, updateWire model wire false)
            else (cId, wire))
        |> Map.ofList

    { model with Wires = newWires }



