package main

import "fmt"
import "bufio"
import "os"
import "strings"
import "strconv"
import "time"

import "runtime/pprof"
import "flag"

import "router"

////////////////////////////////////////////////////////////////////////////////

type Node struct {
	nodeID router.NodeID
	table  router.Table
	links  []*Node
}

func (n *Node) init(nodeID router.NodeID) {
	n.nodeID = nodeID
	n.table.Init(nodeID)
	n.links = append(n.links, n)
}

func linkNodes(m, n *Node) {
	for _, o := range m.links {
		if o.nodeID == n.nodeID {
			// Don't allow duplicates
			return
		}
	}
	m.links = append(m.links, n)
	n.links = append(n.links, m)
}

func makeStoreSquareGrid(sideLength int) map[router.NodeID]*Node {
	store := make(map[router.NodeID]*Node)
	nNodes := sideLength * sideLength
	nodeIDs := make([]router.NodeID, 0, nNodes)
	// TODO shuffle nodeIDs
	for nodeID := 1; nodeID <= nNodes; nodeID++ {
		nodeIDs = append(nodeIDs, router.NodeID(nodeID))
	}
	for _, nodeID := range nodeIDs {
		node := &Node{}
		node.init(nodeID)
		store[nodeID] = node
	}
	for idx := 0; idx < nNodes; idx++ {
		if (idx % sideLength) != 0 {
			linkNodes(store[nodeIDs[idx]], store[nodeIDs[idx-1]])
		}
		if idx >= sideLength {
			linkNodes(store[nodeIDs[idx]], store[nodeIDs[idx-sideLength]])
		}
	}
	return store
}

func loadGraph(path string) map[router.NodeID]*Node {
	f, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer f.Close()
	store := make(map[router.NodeID]*Node)
	s := bufio.NewScanner(f)
	for s.Scan() {
		line := s.Text()
		nodeIDstrs := strings.Split(line, " ")
		nodeIDi0, _ := strconv.Atoi(nodeIDstrs[0])
		nodeIDi1, _ := strconv.Atoi(nodeIDstrs[1])
		nodeID0 := router.NodeID(nodeIDi0)
		nodeID1 := router.NodeID(nodeIDi1)
		if store[nodeID0] == nil {
			node := &Node{}
			node.init(nodeID0)
			store[nodeID0] = node
		}
		if store[nodeID1] == nil {
			node := &Node{}
			node.init(nodeID1)
			store[nodeID1] = node
		}
		linkNodes(store[nodeID0], store[nodeID1])
	}
	return store
}

////////////////////////////////////////////////////////////////////////////////

func idleUntilConverged(store map[router.NodeID]*Node) {
	timeOfLastChange := 0
	step := 0
	// Idle untl the network has converged
	for step-timeOfLastChange < 4*router.TIMEOUT {
		step++
		fmt.Println("Step:", step, "--", "last change:", timeOfLastChange)
		for _, node := range store {
			node.table.Tick()
			for idx, link := range node.links[1:] {
				msg := node.table.CreateMessage(router.Iface(idx))
				for idx, fromNode := range link.links {
					if fromNode == node {
						//fmt.Println("Sending from node", node.nodeID, "to", link.nodeID)
						link.table.HandleMessage(msg, router.Iface(idx))
						break
					}
				}
			}
		}
		//for _, node := range store {
		//  if node.table.DEBUG_isDirty() { timeOfLastChange = step }
		//}
		//time.Sleep(10*time.Millisecond)
	}
}

func testPaths(store map[router.NodeID]*Node) {
	nNodes := len(store)
	nodeIDs := make([]router.NodeID, 0, nNodes)
	for nodeID := range store {
		nodeIDs = append(nodeIDs, nodeID)
	}
	lookups := 0
	count := 0
	start := time.Now()
	for _, source := range store {
		count++
		fmt.Printf("Testing paths from node %d / %d (%d)\n", count, nNodes, source.nodeID)
		for _, dest := range store {
			//if source == dest { continue }
			destLoc := dest.table.GetLocator()
			temp := 0
			for here := source; here != dest; {
				temp++
				if temp > 16 {
					panic("Loop?")
				}
				next := here.links[here.table.Lookup(destLoc)]
				if next == here {
					//for idx, link := range here.links {
					//  fmt.Println("DUMP:", idx, link.nodeID)
					//}
					panic(fmt.Sprintln("Routing Loop:",
						source.nodeID,
						here.nodeID,
						dest.nodeID))
				}
				//fmt.Println("DEBUG:", source.nodeID, here.nodeID, dest.nodeID)
				here = next
				lookups++
			}
		}
	}
	timed := time.Since(start)
	fmt.Printf("%f lookups per second\n", float64(lookups)/timed.Seconds())
}

func dumpStore(store map[router.NodeID]*Node) {
	for _, node := range store {
		fmt.Println("DUMPSTORE:", node.nodeID, node.table.GetLocator())
		node.table.DEBUG_dumpTable()
	}
}

////////////////////////////////////////////////////////////////////////////////

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile `file`")

func main() {
	flag.Parse()
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			panic(fmt.Sprintf("could not create CPU profile: ", err))
		}
		if err := pprof.StartCPUProfile(f); err != nil {
			panic(fmt.Sprintf("could not start CPU profile: ", err))
		}
		defer pprof.StopCPUProfile()
	}
	fmt.Println("Test")
	store := makeStoreSquareGrid(4)
	idleUntilConverged(store)
	dumpStore(store)
	testPaths(store)
	//panic("DYING")
	store = loadGraph("hype-2016-09-19.list")
	idleUntilConverged(store)
	dumpStore(store)
	testPaths(store)
}