package p2p

import "io"
import "net"
import "fmt"
import "time"
import "testing"
import "container/list"
import "encoding/binary"

import "github.com/romana/rlog"
import log "github.com/sirupsen/logrus"

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




// all communications flow in little endian
const LEVIN_SIGNATURE = 0x0101010101012101 //Bender's nightmare
const LEVIN_SIGNATURE_DATA = 0x0102010101011101
const LEVIN_PROTOCOL_VER_0 = 0
const LEVIN_PROTOCOL_VER_1 = 1

const LEVIN_PACKET_REQUEST = 0x00000001
const LEVIN_PACKET_RESPONSE = 0x00000002

// the whole structure should be packed into 33 bytes
type Levin_Header struct {
	Signature        uint64
	CB               uint64 // this contains data size appended to buffer
	ReturnData       bool
	Command          uint32
	ReturnCode       int32
	Flags            uint32
	Protocol_Version uint32
}

// all response will have the signature in big endian form
type Levin_Data_Header struct {
	Signature uint64 // LEVIN_SIGNATURE_DATA
	//Boost_Header byte
	Data []byte
}

// sets  timeout based on connection state, so as stale connections are cleared quickly
func set_timeout(connection *Connection) {
	if connection.State == HANDSHAKE_PENDING {
		connection.Conn.SetReadDeadline(time.Now().Add(20 * time.Second)) // new connections have 20 seconds to handshake
	} else {
		connection.Conn.SetReadDeadline(time.Now().Add(300 * time.Second)) // good connections have 5 mins to communicate
	}
}

/* this is the entire connection handler, all incoming/outgoing connections end up here  */
func Handle_Connection(conn net.Conn, remote_addr *net.TCPAddr, incoming bool) {

	var connection Connection
	var levin_header Levin_Header
	connection.Incoming = incoming
	connection.Conn = conn
	var idle int
	
	
	
	
	connection.Addr = remote_addr //  since we may be connecting via socks, get target IP
	connection.Command_queue = list.New() // init command queue
	connection.State = HANDSHAKE_PENDING
	if incoming {
		connection.logger = logger.WithFields(log.Fields{"RIP": remote_addr.String(), "DIR": "INC"})
	} else {
		connection.logger = logger.WithFields(log.Fields{"RIP": remote_addr.String(), "DIR": "OUT"})
	}
	
	defer func() {
        if r := recover(); r != nil {
            connection.logger.Fatalf("Recovered while handling connection", r)
        }
        }()

	Connection_Add(&connection) // add connection to pool
	if !incoming {
		Send_Handshake(&connection) 	// send handshake
	}

	// goroutine to exit the connection if signalled
	go func() {
		ticker := time.NewTicker(1 * time.Second) // 1 second ticker
		for {
			select {
			case <-ticker.C:
				idle++
				// if idle more than 13 secs, we should send a timed sync
				if idle > 13 {
					if connection.State != HANDSHAKE_PENDING {
						connection.State = IDLE
					}
					Send_Timed_Sync(&connection)
					//connection.logger.Debugf("We should send a timed sync")
					idle = 0
				}
			case <-Exit_Event: // p2p is shutting down, close the connection
				connection.Exit = true
				ticker.Stop() // release resources of timer
				Connection_Delete(&connection)
				conn.Close()
				return // close the connection and close the routine
			}
			if connection.Exit { // release resources of timer
				ticker.Stop()
				Connection_Delete(&connection)
				return
			}
		}
	}()

	for { 
		if connection.Exit {
			connection.logger.Debugf("Connection exited")
			conn.Close()
			return
		}

                // wait and read header
		header_data := make([]byte, 33, 33) // size of levin header
		idle = 0
		rlog.Tracef(10, "waiting to read header bytes from network %s\n", globals.CTXString(connection.logger))
		set_timeout(&connection)
		read_bytes, err := io.ReadFull(conn, header_data)

		if err != nil {
			rlog.Tracef(4, "Error while reading levin header exiting err:%s\n", err)
			connection.Exit = true
			continue
		}
		rlog.Tracef(10, "Read %d bytes from network\n", read_bytes)

		if connection.State != HANDSHAKE_PENDING {
			connection.State = ACTIVE
		}

		err = levin_header.DeSerialize(header_data)

		if err != nil {
			rlog.Tracef(4, "Error while DeSerializing levin header exiting err:%s\n", err)
			connection.Exit = true
			continue
		}

		// read data as per requirement
		data := make([]byte, levin_header.CB, levin_header.CB)
		set_timeout(&connection)
		read_bytes, err = io.ReadFull(conn, data)

		rlog.Tracef(10, "Read %d bytes from network for data \n", read_bytes)
		if err != nil {
			rlog.Tracef(4, "Error while reading levin data exiting err:%s\n", err)
			connection.Exit = true
			continue
		}

		name := COMMAND_NAME[levin_header.Command]
		if name == "" {
			connection.logger.Warnf("No Such command %d exiting\n", levin_header.Command)
			connection.Exit = true
			continue
		}

		//connection.logger.WithFields(log.Fields{
		//	"command": name,
		//	"flags":   levin_header.Flags}).Debugf("Incoming Command")

		if levin_header.Flags == LEVIN_PACKET_RESPONSE {
			if connection.Command_queue.Len() < 1 {
				connection.logger.Warnf("Invalid Response ( we have not queued anything\n")
				connection.Exit = true
				continue
			}

			front_command := connection.Command_queue.Front()
			if levin_header.Command != front_command.Value.(uint32) {
				connection.logger.Warnf("Invalid Response (  we queued some other command\n")
				connection.Exit = true
				continue
			}

			connection.Lock()
			connection.Command_queue.Remove(front_command)
			connection.Unlock()

			switch levin_header.Command {
			case P2P_COMMAND_HANDSHAKE: // Parse incoming handshake response
				Handle_P2P_Handshake_Command_Response(&connection, &levin_header, data)
				// if response is OK, mark conncection as good and add it to list

			case P2P_COMMAND_TIMED_SYNC: // we never send timed response
                               // connection.logger.Infof("Response for timed sync arrived")
                                Handle_P2P_Timed_Sync_Response(&connection, &levin_header, data)

			case P2P_COMMAND_PING: // we never send ping packets

			case P2P_COMMAND_REQUEST_SUPPORT_FLAGS: // we never send flags packet

			}

		}

		if levin_header.Flags == LEVIN_PACKET_REQUEST {

			switch levin_header.Command {
			case P2P_COMMAND_HANDSHAKE: // send response
				connection.logger.Debugf("Incoming handshake command")
				Handle_P2P_Handshake_Command(&connection, &levin_header, data)

			case P2P_COMMAND_REQUEST_SUPPORT_FLAGS: // send reponse
				Handle_P2P_Support_Flags(&connection, &levin_header, data)

			case P2P_COMMAND_TIMED_SYNC:
				Handle_P2P_Timed_Sync(&connection, &levin_header, data)
				// crypto note core protocols commands related to blockchain
				// peer wants to syncronise his chain to ours
			case BC_NOTIFY_REQUEST_CHAIN:
				Handle_BC_Notify_Chain(&connection, &levin_header, data)
				// we want to syncronise our chain to peers
			case BC_NOTIFY_RESPONSE_CHAIN_ENTRY:
				Handle_BC_Notify_Response_Chain_Entry(&connection, &levin_header, data)

			case BC_NOTIFY_REQUEST_GET_OBJECTS: // peer requested some object
				Handle_BC_Notify_Request_GetObjects(&connection, &levin_header, data)
			case BC_NOTIFY_RESPONSE_GET_OBJECTS: // peer responded to our object requests
				Handle_BC_Notify_Response_GetObjects(&connection, &levin_header, data)
			case BC_NOTIFY_NEW_TRANSACTIONS:
				Handle_BC_Notify_New_Transactions(&connection, &levin_header, data)
			case BC_NOTIFY_NEW_BLOCK:
				Handle_BC_Notify_New_Block(&connection, &levin_header, data)

			}
		}
	}
}

/* this operation can never fail */
func SerializeLevinHeader(header Levin_Header) []byte {

	packed_buffer := make([]byte, 33, 33)

	binary.LittleEndian.PutUint64(packed_buffer[0:8], LEVIN_SIGNATURE) // packed 8 bytes
	binary.LittleEndian.PutUint64(packed_buffer[8:16], header.CB)      // packed 8 + 8 bytes
	if header.ReturnData {
		packed_buffer[16] = 1 // packed 8 + 8 + 1
	}

	binary.LittleEndian.PutUint32(packed_buffer[17:17+4], header.Command)            // packed 8+8+1+4 bytes
	binary.LittleEndian.PutUint32(packed_buffer[21:21+4], uint32(header.ReturnCode)) // packed 8+8+1+4 bytes
	binary.LittleEndian.PutUint32(packed_buffer[25:25+4], header.Flags)              // packed 8+8+1+4 bytes
	binary.LittleEndian.PutUint32(packed_buffer[29:29+4], LEVIN_PROTOCOL_VER_1)      // packed 8+8+1+4 bytes

	return packed_buffer
}

func (header Levin_Header) Serialize() ([]byte, int) {
	packed_buffer := make([]byte, 33, 33)

	binary.LittleEndian.PutUint64(packed_buffer[0:8], LEVIN_SIGNATURE) // packed 8 bytes
	binary.LittleEndian.PutUint64(packed_buffer[8:16], header.CB)      // packed 8 + 8 bytes
	if header.ReturnData {
		packed_buffer[16] = 1 // packed 8 + 8 + 1
	}

	binary.LittleEndian.PutUint32(packed_buffer[17:17+4], header.Command)            // packed 8+8+1+4 bytes
	binary.LittleEndian.PutUint32(packed_buffer[21:21+4], uint32(header.ReturnCode)) // packed 8+8+1+4 bytes
	binary.LittleEndian.PutUint32(packed_buffer[25:25+4], header.Flags)              // packed 8+8+1+4 bytes
	binary.LittleEndian.PutUint32(packed_buffer[29:29+4], LEVIN_PROTOCOL_VER_1)      // packed 8+8+1+4 bytes

	return packed_buffer, len(packed_buffer)

}

// extract structure info from hardcoded node
func (header *Levin_Header) DeSerialize(packed_buffer []byte) (err error) {

	if len(packed_buffer) != 33 {
		return fmt.Errorf("Insufficient header bytes")
	}

	header.Signature = binary.LittleEndian.Uint64(packed_buffer[0:8]) // packed 8 bytes

	if header.Signature != LEVIN_SIGNATURE {
		return fmt.Errorf("Incorrect Levin Signature")
	}
	header.CB = binary.LittleEndian.Uint64(packed_buffer[8:16]) // packed 8 + 8 bytes
	if packed_buffer[16] == 0 {
		header.ReturnData = false // packed 8 + 8 + 1
	} else {
		header.ReturnData = true // packed 8 + 8 + 1
	}

	header.Command = binary.LittleEndian.Uint32(packed_buffer[17 : 17+4])             // packed 8+8+1+4 bytes
	header.ReturnCode = (int32)(binary.LittleEndian.Uint32(packed_buffer[21 : 21+4])) // packed 8+8+1+4 bytes
	header.Flags = binary.LittleEndian.Uint32(packed_buffer[25 : 25+4])               // packed 8+8+1+4 bytes
	header.Protocol_Version = binary.LittleEndian.Uint32(packed_buffer[29 : 29+4])    // packed 8+8+1+4 bytes

	return nil

}

func (header Levin_Data_Header) Serialize() ([]byte, int) {

	var packed_buffer []byte
	// if nothing is to be placed

	if len(header.Data) == 0 {
		packed_buffer = make([]byte, 8+2, 8+2)                                  // 10 bytes minimum heade
		binary.LittleEndian.PutUint64(packed_buffer[0:8], LEVIN_SIGNATURE_DATA) // packed 8 bytes
		packed_buffer[8] = 1
		packed_buffer[9] = 0

		return packed_buffer, len(packed_buffer)

	}
	packed_buffer = make([]byte, 8+2+len(header.Data), 8+2+len(header.Data))

	binary.LittleEndian.PutUint64(packed_buffer[0:8], LEVIN_SIGNATURE_DATA) // packed 8 bytes
	packed_buffer[8] = 1
	packed_buffer[9] = 8
	copy(packed_buffer[10:], header.Data)

	return packed_buffer, len(packed_buffer)

}

// extract structure info from hardcoded node
func (header *Levin_Data_Header) DeSerialize(packed_buffer []byte) (err error) {

	if len(packed_buffer) < 10 {
		return fmt.Errorf("Insufficient header bytes")
	}

	header.Signature = binary.LittleEndian.Uint64(packed_buffer[0:8]) // packed 8 bytes

	if header.Signature != LEVIN_SIGNATURE_DATA {
		return fmt.Errorf("WRONG LEVIN_SIGNATURE_DATA")
	}

	if len(packed_buffer)-8 == 2 {
		return nil
	}
	header.Data = make([]byte, len(packed_buffer)-8+2, len(packed_buffer)-8+2)
	// ignore 2 bytes
	//	packed_buffer[8]=1 // version
	//	packed_buffer[9]=8 // boost 8 , this can be anything as per boost level
	copy(header.Data, packed_buffer[10:])

	return nil

}

func DeSerializeLevinHeader(packed_buffer []byte, header *Levin_Header) error {

	if len(packed_buffer) != 33 {
		return fmt.Errorf("Insufficient header bytes")
	}

	header.Signature = binary.LittleEndian.Uint64(packed_buffer[0:8]) // packed 8 bytes
	header.CB = binary.LittleEndian.Uint64(packed_buffer[8:16])       // packed 8 + 8 bytes
	if packed_buffer[16] == 0 {
		header.ReturnData = false // packed 8 + 8 + 1
	} else {
		header.ReturnData = true // packed 8 + 8 + 1
	}

	header.Command = binary.LittleEndian.Uint32(packed_buffer[17 : 17+4])             // packed 8+8+1+4 bytes
	header.ReturnCode = (int32)(binary.LittleEndian.Uint32(packed_buffer[21 : 21+4])) // packed 8+8+1+4 bytes
	header.Flags = binary.LittleEndian.Uint32(packed_buffer[25 : 25+4])               // packed 8+8+1+4 bytes
	header.Protocol_Version = binary.LittleEndian.Uint32(packed_buffer[29 : 29+4])    // packed 8+8+1+4 bytes

	return nil
}

func TestSerializeDeserialize(t *testing.T) {

}
