package p2p


import "bytes"
import "encoding/binary"

import "github.com/romana/rlog"

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



/* the data structure which needs to be serialised is    defined in cryptonote_protocol_defs.h
* struct request
   {
     uint64_t start_height;
     uint64_t total_height;
     uint64_t cumulative_difficulty;
     std::list<crypto::hash> m_block_ids;

     BEGIN_KV_SERIALIZE_MAP()
       KV_SERIALIZE(cumulative_difficulty)
       KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids)
       KV_SERIALIZE(start_height)
       KV_SERIALIZE(total_height)
     END_KV_SERIALIZE_MAP()
   };
*/

// This request only comes  when we have sent the BC_NOTIFY_REQUEST_CHAIN command
// handle BC_NOTIFY_RESPONSE_CHAIN_ENTRY
func Handle_BC_Notify_Response_Chain_Entry(connection *Connection,
	i_command_header *Levin_Header, buf []byte) {

	// deserialize data header
	var i_data_header Levin_Data_Header // incoming data header

	err := i_data_header.DeSerialize(buf)

	if err != nil {
		//fmt.Printf("We should destroy connection here, data header cnot deserialized")
		rlog.Tracef(4, "Data header deserialisation failed. Disconnect peer \n")
		connection.Exit = true
		return
	}

	pos := bytes.Index(i_data_header.Data, []byte("cumulative_difficulty")) // at this point to
	if pos == -1 {
		rlog.Tracef(4, "Cumulative difficulty deserialisation failed. Disconnect peer \n")
                connection.Exit = true
		return
	}

	cumulative_difficulty := binary.LittleEndian.Uint64(i_data_header.Data[pos+22:])

	rlog.Tracef(4, "Cumalative difficulty %d  %x\n", cumulative_difficulty, cumulative_difficulty)

	pos = bytes.Index(i_data_header.Data, []byte("start_height")) // at this point to
	if pos == -1 {
		panic("start_height not found, its mandatory\n")
                
	}

	start_height := binary.LittleEndian.Uint64(i_data_header.Data[pos+13:])

	rlog.Tracef(4, "start_height %d  %x\n", start_height, start_height)

	pos = bytes.Index(i_data_header.Data, []byte("m_block_ids")) // at this point to
	if pos == -1 {
		panic("m_block_ids not found, its mandatory\n")
	}

	// decode  data length ( though we know it from buffer size, but still verify it )
	buf = i_data_header.Data[pos+11+1:]
	data_length, done := Decode_Boost_Varint(buf)
	//fmt.Printf("data length %d ,   hex  %x\n", data_length, buf[:8])
	buf = buf[done:]

	if data_length == 0 {
		rlog.Tracef(4, "Peer says it does not have even genesis block, so disconnect")
		connection.Exit = true
		return
	}
	if (data_length % 32) != 0 { // sanity check
		rlog.Tracef(2, "We should destroy connection here, packet mismatch")
                connection.Exit = true
		return
	}

	rlog.Tracef(4, "Number of Blocks id in chain BC_NOTIFY_RESPONSE_CHAIN_ENTRY %d \n", data_length/32)

	var block_list []crypto.Hash

	for i := uint64(0); i < data_length/32; i++ {
		var bhash crypto.Hash
		copy(bhash[:], buf[i*32:(i+1)*32])
		// only request block that we donot have

		if chain.Get_Height() < 20 {
			block_list = append(block_list, bhash)
			rlog.Tracef(5, "%2d hash  %x\n", i, bhash[:])
		} else {
			if !chain.Block_Exists(bhash) {
				block_list = append(block_list, bhash)
				rlog.Tracef(5, "%2d hash  %x\n", i, bhash[:])
			}
		}

	}

	// server will kill us, if we queue more than 1000 blocks
	if len(block_list) > 400 {
		block_list = block_list[:399]
	}

	// make sure the genesis block is same
	// if peer provided us a genesis block make sure, its ours
	if start_height == 0 && block_list[0] != globals.Config.Genesis_Block_Hash {
		rlog.Tracef(4, "Peer's genesis block is different from our, so disconnect")
		connection.Exit = true
		return
	}

	// we must queue the hashes so as to fetch them

	//block_list = block_list[:0]
	var hash crypto.Hash

	/*
	   //big_block ,_ := hex.DecodeString("9ba23efe505f9674dc24c150edbdbe57abc3ec6636aa4c1659e811b389c0b30b") // zero tx
	   //big_block ,_ := hex.DecodeString("14371eeddca0f3ce9b992b3e2a0e482920497d87dd20002456f3a844b04a3318") // single tx

	   big_block ,_ := hex.DecodeString("a31a17bb26b2ec37479ee3a02f53dd94860611457811d0c23ce892f4e87e1697") // 4 tx

	   copy(hash[:],big_block[:32])
	   block_list = append(block_list, hash)


	   big_tx, _ := hex.DecodeString("af6f12d56f32f58623a834c4f12c5443976346a2f877ef78798c39496bd00559") //  single tx





	   var tx_list []ringct.Hash
	   copy(hash[:],big_tx[:32])
	   tx_list = append(tx_list, hash)
	*/
	_ = hash
	var tx_list []crypto.Hash

	/*if len(block_list) > 5 {
	    block_list= block_list[:5]
	}*/

	Send_BC_Notify_Request_GetObjects(connection, block_list, tx_list[:0])

}

// header from boost packet
/* header bytes
0000   01 11 01 01 01 01 02 01 01 10 15 63 75 6d 75 6c  ...........cumul
0010   61 74 69 76 65 5f 64 69 66 66 69 63 75 6c 74 79  ative_difficulty
0020   05 ab 61 00 00 00 00 00 00 0b 6d 5f 62 6c 6f 63  ..a.......m_bloc
0030   6b 5f 69 64 73 0a 82 72 02 00 63 34 12 de 21 ea  k_ids..r..c4..!.


// suffix_bytes
44d0   4c 16 59 e8 11 b3 89 c0 b3 0b 0c 73 74 61 72 74  L.Y........start
44e0   5f 68 65 69 67 68 74 05 00 00 00 00 00 00 00 00  _height.........
44f0   0c 74 6f 74 61 6c 5f 68 65 69 67 68 74 05 e5 04  .total_height...
4500   00 00 00 00 00 00                                ......
*/
// send the PEER the blocks he needs to download our version of chain
// this is send every 5 seconds
func Send_BC_Notify_Response_Chain_Entry(connection *Connection, block_list []crypto.Hash, start_height, current_height, diff uint64) {

	connection.Lock()

	header_bytes := []byte{0x01, 0x11, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x10, 0x15, 0x63, 0x75, 0x6d, 0x75, 0x6c,
		0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79,
		0x05, 0xab, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x6d, 0x5f, 0x62, 0x6c, 0x6f, 0x63,
		0x6b, 0x5f, 0x69, 0x64, 0x73, 0x0a}

	suffix_bytes := []byte{0x0c, 0x73, 0x74, 0x61, 0x72, 0x74,
		0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x05, 0xe5, 0x04,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00}

	binary.LittleEndian.PutUint64(header_bytes[33:], diff)
	binary.LittleEndian.PutUint64(suffix_bytes[14:], start_height)
	binary.LittleEndian.PutUint64(suffix_bytes[36:], current_height)

	// now append boost variant length, and then append all the hashes

	buf := make([]byte, 8, 8)
	done := Encode_Boost_Varint(buf, uint64(len(block_list)*32)) // encode length of buffer
	buf = buf[:done]

	var o_command_header Levin_Header

	data_bytes := append(header_bytes, buf...)

	// convert and append all hashes to bytes
	for _, hash := range block_list {
		data_bytes = append(data_bytes, hash[:32]...)
	}

	data_bytes = append(data_bytes, suffix_bytes...)

	o_command_header.CB = uint64(len(data_bytes))

	o_command_header.Command = BC_NOTIFY_RESPONSE_CHAIN_ENTRY
	o_command_header.ReturnData = false
	o_command_header.Flags = LEVIN_PACKET_REQUEST

	o_command_header_bytes, _ := o_command_header.Serialize()

	connection.Conn.Write(o_command_header_bytes)
	connection.Conn.Write(data_bytes)

	//fmt.Printf("len of command header %d\n", len(o_command_header_bytes))
	//fmt.Printf("len of data header %d\n", len(data_bytes))

	connection.Unlock()
}
