package plan

import "sort"

type artifactScopeMembership struct {
	frame   ArtifactScopeFrame
	members map[string]bool
}

func lowerArtifactScopes(analysis compilerAnalysis) ([]ArtifactScope, map[string][]ArtifactScopeFrame, map[string][]ArtifactScopeTransition) {
	memberships := make([]artifactScopeMembership, 0)
	for _, scope := range analysis.parallelScopes {
		terminalBranches := map[string][]string{}
		for _, branch := range scope.branches {
			for _, terminal := range branch.failedTerminals {
				terminalBranches[terminal] = append(terminalBranches[terminal], branch.port)
			}
		}
		for _, branch := range scope.branches {
			members := map[string]bool{}
			for _, member := range branch.members {
				members[member] = true
			}
			for _, terminal := range branch.failedTerminals {
				if len(terminalBranches[terminal]) == 1 {
					members[terminal] = true
				}
			}
			memberships = append(memberships, artifactScopeMembership{
				frame:   ArtifactScopeFrame{Kind: ArtifactScopeParallel, OwnerID: scope.ownerID, Branch: branch.port},
				members: members,
			})
		}
	}
	for _, scope := range analysis.loopScopes {
		members := make(map[string]bool, len(scope.members))
		for _, member := range scope.members {
			members[member] = true
		}
		memberships = append(memberships, artifactScopeMembership{
			frame:   ArtifactScopeFrame{Kind: ArtifactScopeLoop, OwnerID: scope.ownerID},
			members: members,
		})
	}
	for _, scope := range analysis.fanOutScopes {
		members := make(map[string]bool, len(scope.members)+len(scope.failedTerminals))
		for _, member := range scope.members {
			members[member] = true
		}
		for _, terminal := range scope.failedTerminals {
			members[terminal] = true
		}
		memberships = append(memberships, artifactScopeMembership{
			frame:   ArtifactScopeFrame{Kind: ArtifactScopeFanOut, OwnerID: scope.ownerID},
			members: members,
		})
	}

	nodePaths := map[string][]ArtifactScopeFrame{}
	for _, membership := range memberships {
		for nodeID := range membership.members {
			nodePaths[nodeID] = append(nodePaths[nodeID], membership.frame)
		}
	}
	membershipByFrame := func(frame ArtifactScopeFrame) map[string]bool {
		for _, membership := range memberships {
			if membership.frame == frame {
				return membership.members
			}
		}
		return nil
	}
	for nodeID, path := range nodePaths {
		sort.Slice(path, func(i, j int) bool {
			left, right := membershipByFrame(path[i]), membershipByFrame(path[j])
			if len(left) != len(right) {
				return len(left) > len(right)
			}
			if path[i].OwnerID != path[j].OwnerID {
				return path[i].OwnerID < path[j].OwnerID
			}
			if path[i].Kind != path[j].Kind {
				return path[i].Kind < path[j].Kind
			}
			return path[i].Branch < path[j].Branch
		})
		nodePaths[nodeID] = path
	}

	transitions := map[string][]ArtifactScopeTransition{}
	addTransition := func(edgeID string, transition ArtifactScopeTransition) {
		transitions[edgeID] = append(transitions[edgeID], transition)
	}
	scopes := make([]ArtifactScope, 0, len(analysis.parallelScopes)+len(analysis.loopScopes)+len(analysis.fanOutScopes))
	for _, source := range analysis.parallelScopes {
		branches := make([]ArtifactParallelBranch, 0, len(source.branches))
		for _, branch := range source.branches {
			compiled := ArtifactParallelBranch{
				Port:            branch.port,
				Members:         append([]string{}, branch.members...),
				EntryEdges:      append([]string{}, branch.entryEdges...),
				SuccessArrivals: append([]string{}, branch.successArrivals...),
				FailedTerminals: append([]string{}, branch.failedTerminals...),
			}
			branches = append(branches, compiled)
			for _, edgeID := range branch.entryEdges {
				addTransition(edgeID, ArtifactScopeTransition{ScopeKind: ArtifactScopeParallel, OwnerID: source.ownerID, Action: "enter", Branch: branch.port})
			}
			for _, edgeID := range branch.successArrivals {
				addTransition(edgeID, ArtifactScopeTransition{ScopeKind: ArtifactScopeParallel, OwnerID: source.ownerID, Action: "arrive", Branch: branch.port})
			}
		}
		scopes = append(scopes, ArtifactScope{
			Kind:       ArtifactScopeParallel,
			OwnerID:    source.ownerID,
			ParentPath: append([]ArtifactScopeFrame{}, nodePaths[source.ownerID]...),
			Parallel:   &ArtifactParallelScope{ActivationSet: source.activation, JoinID: source.joinID, Mode: source.mode, Branches: branches},
		})
	}
	for _, source := range analysis.loopScopes {
		for _, edgeID := range source.bodyEntryEdges {
			addTransition(edgeID, ArtifactScopeTransition{ScopeKind: ArtifactScopeLoop, OwnerID: source.ownerID, Action: "body"})
		}
		for _, edgeID := range source.continueEdges {
			addTransition(edgeID, ArtifactScopeTransition{ScopeKind: ArtifactScopeLoop, OwnerID: source.ownerID, Action: "continue"})
		}
		for _, edgeID := range source.breakEdges {
			addTransition(edgeID, ArtifactScopeTransition{ScopeKind: ArtifactScopeLoop, OwnerID: source.ownerID, Action: "break"})
		}
		for _, edgeID := range source.doneEdges {
			addTransition(edgeID, ArtifactScopeTransition{ScopeKind: ArtifactScopeLoop, OwnerID: source.ownerID, Action: "done"})
		}
		for _, edgeID := range source.failedEdges {
			addTransition(edgeID, ArtifactScopeTransition{ScopeKind: ArtifactScopeLoop, OwnerID: source.ownerID, Action: "failed"})
		}
		scopes = append(scopes, ArtifactScope{
			Kind:       ArtifactScopeLoop,
			OwnerID:    source.ownerID,
			ParentPath: append([]ArtifactScopeFrame{}, nodePaths[source.ownerID]...),
			Loop: &ArtifactLoopScope{
				BodyEntryEdges: append([]string{}, source.bodyEntryEdges...),
				Members:        append([]string{}, source.members...),
				ContinueEdges:  append([]string{}, source.continueEdges...),
				BreakEdges:     append([]string{}, source.breakEdges...),
				DoneEdges:      append([]string{}, source.doneEdges...),
				FailedEdges:    append([]string{}, source.failedEdges...),
			},
		})
	}
	for _, source := range analysis.fanOutScopes {
		for _, edgeID := range source.bodyEntryEdges {
			addTransition(edgeID, ArtifactScopeTransition{ScopeKind: ArtifactScopeFanOut, OwnerID: source.ownerID, Action: "item"})
		}
		for _, edgeID := range source.successArrivals {
			addTransition(edgeID, ArtifactScopeTransition{ScopeKind: ArtifactScopeFanOut, OwnerID: source.ownerID, Action: "arrive"})
		}
		scopes = append(scopes, ArtifactScope{
			Kind:       ArtifactScopeFanOut,
			OwnerID:    source.ownerID,
			ParentPath: append([]ArtifactScopeFrame{}, nodePaths[source.ownerID]...),
			FanOut: &ArtifactFanOutScope{
				JoinID:          source.joinID,
				Members:         append([]string{}, source.members...),
				BodyEntryEdges:  append([]string{}, source.bodyEntryEdges...),
				SuccessArrivals: append([]string{}, source.successArrivals...),
				FailedTerminals: append([]string{}, source.failedTerminals...),
			},
		})
	}
	sort.Slice(scopes, func(i, j int) bool {
		if scopes[i].OwnerID != scopes[j].OwnerID {
			return scopes[i].OwnerID < scopes[j].OwnerID
		}
		return scopes[i].Kind < scopes[j].Kind
	})
	for edgeID, values := range transitions {
		sort.Slice(values, func(i, j int) bool {
			if values[i].OwnerID != values[j].OwnerID {
				return values[i].OwnerID < values[j].OwnerID
			}
			if values[i].ScopeKind != values[j].ScopeKind {
				return values[i].ScopeKind < values[j].ScopeKind
			}
			if values[i].Action != values[j].Action {
				return values[i].Action < values[j].Action
			}
			return values[i].Branch < values[j].Branch
		})
		transitions[edgeID] = values
	}
	return scopes, nodePaths, transitions
}
