package p2p

/* this file implements the connection pool manager, keeping a list of active connections etc
 * this will also ensure that a single IP is connected only once
 *
 */
import "fmt"
import "net"
import "sync"
import "container/list"

import log "github.com/sirupsen/logrus"

import "github.com/deroproject/derosuite/crypto"


// any connection incoming/outgoing can only be in this state
type Conn_State string
const (
	HANDSHAKE_PENDING Conn_State = "Pending"
	IDLE                         = "Idle"
	ACTIVE                       = "Active"
)

// This structure is used to do book keeping for the connection and keeps other DATA related to peer
type Connection struct {
	Incoming              bool // is connection incoming or outgoing
	Addr                  *net.TCPAddr // endpoint on the other end
	Port                  uint32 // port advertised by other end as its server,if it's 0 server cannot accept connections
	Peer_ID               uint64 // Remote peer id
	Last_Height           uint64 // last height sent by peer
	Top_Version           uint64 // current hard fork version supported by peer
	Exit                  bool  // Exit marker that connection needs to be killed
	State                 Conn_State // state of the connection
	Top_ID                crypto.Hash // top block id of the connection
	Cumulative_Difficulty uint64 // cumulative difficulty of top block of peer
	logger                *log.Entry // connection specific logger
	Requested_Objects     [][32]byte // currently unused as we sync up with a single peer at a time
	Conn                  net.Conn   // actual object to talk
	Command_queue         *list.List // LEVIN protocol is syncronous
	sync.Mutex
}

var connection_map = map[string]*Connection{}
var connection_mutex sync.Mutex

func Key(ip net.IP) string {
	return string(ip.To16()) // Simple []byte => string conversion
}

// check whether an IP is in the map already
func IsConnected(ip net.IP) bool {
	connection_mutex.Lock()
	defer connection_mutex.Unlock()

	if _, ok := connection_map[Key(ip)]; ok {
		return true
	}
	return false
}

// add connection to  map
func Connection_Add(c *Connection) {
	connection_mutex.Lock()
	defer connection_mutex.Unlock()
	connection_map[Key(c.Addr.IP)] = c
}

// add connection to  map
func Connection_Delete(c *Connection) {
	connection_mutex.Lock()
	defer connection_mutex.Unlock()
	delete(connection_map, Key(c.Addr.IP))
}

// prints all the connection info to screen
func Connection_Print() {
	connection_mutex.Lock()
	defer connection_mutex.Unlock()
	fmt.Printf("Connection info for peers\n")
	fmt.Printf("%-20s %-16s %-5s %-7s %9s %3s\n", "Remote Addr", "PEER ID", "PORT", " State", "Height","DIR")
	for _, v := range connection_map {
                dir := "OUT"
                if v.Incoming {
                    dir = "INC"
                }
		fmt.Printf("%-20s %16x %5d %7s %9d %s\n", v.Addr.IP, v.Peer_ID, v.Port, v.State, v.Last_Height, dir)
	}

}

// for continuos update on command line, get the maximum height of all peers
func Best_Peer_Height() (best_height uint64) {
	connection_mutex.Lock()
	for _, v := range connection_map {
		if v.Last_Height > best_height {
			best_height = v.Last_Height
		}
	}
	connection_mutex.Unlock()
	return
}

// this function return peer count which have successful handshake
func Peer_Count() (Count uint64) {
	connection_mutex.Lock()
	for _, v := range connection_map {
		if v.State != HANDSHAKE_PENDING {
			Count++
		}
	}
	connection_mutex.Unlock()
	return
}

// this returns count of peers in both directions
func Peer_Direction_Count() (Incoming uint64, Outgoing uint64) {
	connection_mutex.Lock()
	for _, v := range connection_map {
		if v.State != HANDSHAKE_PENDING {
			if v.Incoming{
                            Incoming++
                        }else{
                            Outgoing++   
                        }
		}
	}
	connection_mutex.Unlock()
	return
}
