package blockchain

import "fmt"
import "bytes"
import "encoding/binary"

import "github.com/romana/rlog"

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



const TXIN_GEN = byte(0xff)
const TXIN_TO_SCRIPT = byte(0)
const TXIN_TO_SCRIPTHASH = byte(1)
const TXIN_TO_KEY = byte(2)

const TXOUT_TO_SCRIPT = byte(0)
const TXOUT_TO_SCRIPTHASH = byte(1)
const TXOUT_TO_KEY = byte(2)

var TX_IN_NAME = map[byte]string{
	TXIN_GEN:           "Coinbase",
	TXIN_TO_SCRIPT:     "To Script",
	TXIN_TO_SCRIPTHASH: "To Script hash",
	TXIN_TO_KEY:        "To key",
}

const TRANSACTION = byte(0xcc)
const BLOCK = byte(0xbb)

/*
VARIANT_TAG(binary_archive, cryptonote::txin_to_script, 0x0);
VARIANT_TAG(binary_archive, cryptonote::txin_to_scripthash, 0x1);
VARIANT_TAG(binary_archive, cryptonote::txin_to_key, 0x2);
VARIANT_TAG(binary_archive, cryptonote::txout_to_script, 0x0);
VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1);
VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2);
VARIANT_TAG(binary_archive, cryptonote::transaction, 0xcc);
VARIANT_TAG(binary_archive, cryptonote::block, 0xbb);
*/
/* outputs */

type Txout_to_script struct {
	// std::vector<crypto::public_key> keys;
	//  std::vector<uint8_t> script;

	Keys   [][32]byte
	Script []byte

	/*  BEGIN_SERIALIZE_OBJECT()
	      FIELD(keys)
	      FIELD(script)
	    END_SERIALIZE()

	*/
}

type Txout_to_scripthash struct {
	//crypto::hash hash;
	Hash [32]byte
}

type Txout_to_key struct {
	Key  crypto.Key
//	Mask [32]byte `json:"-"`
	/*txout_to_key() { }
	  txout_to_key(const crypto::public_key &_key) : key(_key) { }
	  crypto::public_key key;*/

}

// there can be only 4 types if inputs

// used by miner
type Txin_gen struct {
	Height uint64 // stored as varint
}

type Txin_to_script struct {
	Prev    [32]byte
	Prevout uint64
	Sigset  []byte

	/* BEGIN_SERIALIZE_OBJECT()
	     FIELD(prev)
	     VARINT_FIELD(prevout)
	     FIELD(sigset)
	   END_SERIALIZE()
	*/
}

type Txin_to_scripthash struct {
	Prev    [32]byte
	Prevout uint64
	Script  Txout_to_script
	Sigset  []byte

	/* BEGIN_SERIALIZE_OBJECT()
	     FIELD(prev)
	     VARINT_FIELD(prevout)
	     FIELD(script)
	     FIELD(sigset)
	   END_SERIALIZE()
	*/
}

type Txin_to_key struct {
	Amount      uint64
	Key_offsets []uint64 // this is encoded as a varint for length and then all offsets are stored as varint
	//crypto::key_image k_image;      // double spending protection
	K_image crypto.Hash `json:"k_image"` // key image

	/* BEGIN_SERIALIZE_OBJECT()
	     VARINT_FIELD(amount)
	     FIELD(key_offsets)
	     FIELD(k_image)
	   END_SERIALIZE()
	*/
}

type Txin_v interface{} // it can only be txin_gen, txin_to_script, txin_to_scripthash, txin_to_key

type Tx_out struct {
	Amount uint64
	Target interface{} // txout_target_v ;, it can only be  txout_to_script, txout_to_scripthash, txout_to_key

	/* BEGIN_SERIALIZE_OBJECT()
	     VARINT_FIELD(amount)
	     FIELD(target)
	   END_SERIALIZE()
	*/

}

// the core transaction
type Transaction_Prefix struct {
	Version     uint64 `json:"version"`
	Unlock_Time uint64 `json:"unlock_time"`
	Vin         []Txin_v
	Vout        []Tx_out
	Extra       []byte
	ExtraType   byte `json:"-"`
}

type Transaction struct {
	Transaction_Prefix
	// same as Transaction_Prefix
	// Signature  not sure of what form
	Signature []Signature_v1 `json:"-"` // old format, the array size is always equal to vin length,
	//Signature_RCT RCT_Signature  // version 2

	RctSignature *ringct.RctSig
	Expanded     bool `json:"-"`
}

func (tx *Transaction) GetHash() (result crypto.Hash) {
	switch tx.Version {

	case 1:
		result = crypto.Hash(crypto.Keccak256(tx.SerializeHeader()))

	case 2:
		// version 2 requires first computing 3 separate hashes
		// prefix, rctBase and rctPrunable
		// and then hashing the hashes together to get the final hash
		prefixHash := tx.GetPrefixHash()
		rctBaseHash := tx.RctSignature.BaseHash()
		rctPrunableHash := tx.RctSignature.PrunableHash()
		result = crypto.Hash(crypto.Keccak256(prefixHash[:], rctBaseHash[:], rctPrunableHash[:]))
	default:
		panic("Transaction version cannot be zero")
		return
	}

	return
}

func (tx *Transaction) GetPrefixHash() (result crypto.Hash) {
	result = crypto.Keccak256(tx.SerializeHeader())
	return result
}

func (tx *Transaction) DeserializeHeader(buf []byte) (err error) {

	Key_offset_count := uint64(0) // used to calculate expected signatures in v1

	Mixin := -1

	tx.Clear() // clear existing

	//Mixin_count := 0 // for signature purpose

	done := 0
	tx.Version, done = binary.Uvarint(buf)
	if done <= 0 {
		return fmt.Errorf("Invalid Version in Transaction\n")
	}

	rlog.Tracef(10, "transaction version %d\n", tx.Version)

	buf = buf[done:]
	tx.Unlock_Time, done = binary.Uvarint(buf)
	if done <= 0 {
		return fmt.Errorf("Invalid Unlock_Time in Transaction\n")
	}
	buf = buf[done:]

	// parse vin length
	vin_length, done := binary.Uvarint(buf)
	if done <= 0 {
		return fmt.Errorf("Invalid Vin length in Transaction\n")
	}
	buf = buf[done:]

	if vin_length == 0 {
		return fmt.Errorf("Vin input cannot be zero in Transaction\n")

	}

	coinbase_done := false

	rlog.Tracef(10, "vin length %d\n", vin_length)

	for i := uint64(0); i < vin_length; i++ {

		vin_type := buf[0]

		buf = buf[1:] // consume 1 more byte

		rlog.Tracef(10, "Processing i %d  vin_type %s  hex %x\n", i, TX_IN_NAME[vin_type], buf[:40])

		switch vin_type {
		case TXIN_GEN:
			rlog.Tracef(10, "Coinbase transaction\n")

			if coinbase_done {
				return fmt.Errorf("Transaction cannot have multiple coin base transaction\n")

			}
			var current_vin Txin_gen
			current_vin.Height, done = binary.Uvarint(buf)
			if done <= 0 {
				return fmt.Errorf("Invalid Height for Txin_gen vin in Transaction\n")
			}
			buf = buf[done:]
			tx.Vin = append(tx.Vin, current_vin)

			coinbase_done = true // we can no longer have coin base

		case TXIN_TO_SCRIPT:
			panic("TXIN_TO_SCRIPT not implemented")
		case TXIN_TO_SCRIPTHASH:
			panic("TXIN_TO_SCRIPTHASH not implemented")
		case TXIN_TO_KEY:
			var current_vin Txin_to_key

			// parse Amount
			current_vin.Amount, done = binary.Uvarint(buf)
			if done <= 0 {
				return fmt.Errorf("Invalid Amount for Txin_to_key vin in Transaction\n")
			}
			buf = buf[done:]

			//fmt.Printf("Remaining data %x\n", buf[:20]);

			mixin_count, done := binary.Uvarint(buf)
			if done <= 0 {
				return fmt.Errorf("Invalid offset_count for Txin_to_key vin in Transaction\n")
			}
			buf = buf[done:]

			// safety check mixin cannot be larger than say x

			if mixin_count > 40 {
				return fmt.Errorf("Mixin cannot be larger than 40\n")
			}

			if Mixin < 0 {
				Mixin = int(mixin_count)
			}

			if Mixin != int(mixin_count) { // all vins must have same mixin
				return fmt.Errorf("Different mixin in Transaction\n")

			}

			//Mixin_input_count += Mixin

			for j := uint64(0); j < mixin_count; j++ {
				offset, done := binary.Uvarint(buf)
				if done <= 0 {
					return fmt.Errorf("Invalid key offset for Txin_to_key vin in Transaction\n")
				}
				buf = buf[done:]
				current_vin.Key_offsets = append(current_vin.Key_offsets, offset)
			}

			Key_offset_count += mixin_count

			copy(current_vin.K_image[:], buf[:32]) // copy key image

			buf = buf[32:] // consume key image bytes

			tx.Vin = append(tx.Vin, current_vin)

			// panic("TXIN_TO_KEY not implemented")

		default:
			panic("Invalid VIN type in Transaction")
			fmt.Errorf("Invalid VIN type in Transaction\n")

		}

	}

	//fmt.Printf("TX before vout %+v\n", tx)

	//fmt.Printf("buf before vout length %x\n", buf)
	vout_length, done := binary.Uvarint(buf)
	if done <= 0 {
		return fmt.Errorf("Invalid Vout length in Transaction\n")
	}
	buf = buf[done:]

	if vout_length == 0 {
		return fmt.Errorf("Vout cannot be zero in Transaction\n")
	}

	for i := uint64(0); i < vout_length; i++ {

		// amount is decoded earlier

		amount, done := binary.Uvarint(buf)
		if done <= 0 {
			return fmt.Errorf("Invalid Amount in Transaction\n")
		}
		buf = buf[done:]

		// decode vout type

		vout_type := buf[0]
		buf = buf[1:] // consume 1 more byte

		rlog.Tracef(10, "Vout Amount length %d  vout type %d   \n", amount, vout_type)

		if tx.Version == 1 && amount == 0 { // version 2 can have any amount
			return fmt.Errorf("Amount cannot be zero in Transaction\n")
		}
		switch vout_type {
		case TXOUT_TO_SCRIPT:
			//fmt.Printf("out to script\n")
			panic("TXOUT_TO_SCRIPT not implemented")
		case TXOUT_TO_SCRIPTHASH:
			//fmt.Printf("out to scripthash\n")
			var current_vout Txout_to_scripthash
			copy(current_vout.Hash[:], buf[0:32])
			tx.Vout = append(tx.Vout, Tx_out{Amount: amount, Target: current_vout})

			buf = buf[32:]

			//panic("TXOUT_TO_SCRIPTHASH not implemented")
		case TXOUT_TO_KEY:
			//fmt.Printf("out to key\n")

			var current_vout Txout_to_key

			copy(current_vout.Key[:], buf[0:32])
			buf = buf[32:]

			//Mixin_input_count++

			tx.Vout = append(tx.Vout, Tx_out{Amount: amount, Target: current_vout})

		default:
			fmt.Errorf("Invalid VOUT type in Transaction\n")

		}

	}

	// fmt.Printf("Extra %x\n", buf)
	// decode extra
	extra_length, done := binary.Uvarint(buf)
	if done <= 0 {
		return fmt.Errorf("Invalid Extra length in Transaction\n")
	}
	buf = buf[done:]

	// BUG extra needs to be processed in a loop till we load all extra fields

	//tx.ExtraType = buf[0]
	//    buf = buf[1:] // consume 1 more byte

	//    extra_length--

	rlog.Tracef(8, "extra len %d  have %d \n", extra_length, len(buf))
	tx.Extra = buf[:extra_length]

	// whatever is leftover is signature
	buf = buf[extra_length:] // consume more bytes

	switch tx.Version {
	case 1: // old style signatures, load value
		for i := uint64(0); i < Key_offset_count; i++ {
			var s Signature_v1
			copy(s.R[:], buf[:32])
			copy(s.C[:], buf[32:64])
			tx.Signature = append(tx.Signature, s)
			buf = buf[SIGNATURE_V1_LENGTH:]
		}
	case 2:
		bufreader := bytes.NewReader(buf)

		Mixin -= 1 // one is ours, rest are mixin

		tx.RctSignature, err = ringct.ParseRingCtSignature(bufreader, len(tx.Vin), len(tx.Vout), Mixin)
		if err != nil {
			return err
		}
	}

	/* we must deserialize signature some where else




	   //fmt.Printf("extra bytes %x\n",buf)

	   //fmt.Printf("signature len %d  should be %d\n",len(buf),len(tx.Vin)*SIGNATURE_V1_LENGTH)
	   fmt.Printf("signature len %d  should be %d\n",len(buf),Key_offset_count*SIGNATURE_V1_LENGTH)



	   switch tx.Version {
	       case 1 : // old style signatures, load value
	                for i := uint64(0); i < Key_offset_count;i++{
	                    var s Signature_v1
	                    copy(s.R[:],buf[:32])
	                    copy(s.C[:],buf[32:64])
	                    tx.Signature = append(tx.Signature, s)
	                    buf = buf[SIGNATURE_V1_LENGTH:]
	                }
	       case 2:
	                tx.Signature_RCT.Type, done = binary.Uvarint(buf)
	               if done <= 0 {
	                       return fmt.Errorf("Invalid RCT signature in Transaction\n")
	               }
	                   buf = buf[done:]

	                   switch tx.Signature_RCT.Type {

	                       case 0 : // no signature break

	                       case 1 :



	                   tx.Signature_RCT.TxnFee, done = binary.Uvarint(buf)
	               if done <= 0 {
	                       return fmt.Errorf("Invalid txn fee in Transaction\n")
	               }
	                   buf = buf[done:]

	                   fmt.Printf("RCT signature type %d  Fee %d\n",tx.Signature_RCT.Type, tx.Signature_RCT.TxnFee)


	                   // how many masked inputs depends on number of masked outouts
	                   for i := (0); i < len(tx.Vout);i++{
	                       // read masked input
	                       var info ECDHinfo
	                       copy(info.Mask[:], buf[0:32])
	                       copy(info.Amount[:], buf[32:64])
	                       tx.Signature_RCT.Amounts = append(tx.Signature_RCT.Amounts, info)
	                      buf = buf[64:]
	                   }

	                   // now parse the public keys
	                   for i := (0); i < len(tx.Vout);i++{
	                       // read masked input
	                       var tmp [32]byte
	                       copy(tmp[:], buf[0:32])

	                       tx.Signature_RCT.OutPK = append(tx.Signature_RCT.OutPK, tmp)
	                      buf = buf[32:]
	                   }

	                       case 2 : // panic("ringct type 2 currently not handled")


	                       default:
	                           panic("unknown signature style")


	                   }

	       default:
	           panic("unknown transaction version \n")



	   }


	*/

	rlog.Tracef(8, "TX deserialized %+v\n", tx)

	/*
		data.Local_time = binary.LittleEndian.Uint64( buf[24:], )

		 data.Local_Port = binary.LittleEndian.Uint32( buf[41:])

		_ = data.Network_UUID.UnmarshalBinary(buf[58:58+16])


		data.Peer_ID = binary.LittleEndian.Uint64( buf[83:] )
	*/
	return nil //fmt.Errorf("Done Transaction\n")

}

// calculated prefi has signature
func (tx *Transaction) PrefixHash() {

}

// calculated prefi has signature
func (tx *Transaction) Clear() {
	// clean the transaction everything
	tx.Version = 0
	tx.Unlock_Time = 0
	tx.Vin = tx.Vin[:0]
	tx.Vout = tx.Vout[:0]
	tx.Extra = tx.Extra[:0]

}

func (tx *Transaction) SerializeHeader() []byte {

	var serialised_header bytes.Buffer

	buf := make([]byte, binary.MaxVarintLen64)

	n := binary.PutUvarint(buf, tx.Version)
	serialised_header.Write(buf[:n])

	n = binary.PutUvarint(buf, tx.Unlock_Time)
	serialised_header.Write(buf[:n])

	/*if len(tx.Vin) < 1 {
		panic("No vins")
	}*/

	n = binary.PutUvarint(buf, uint64(len(tx.Vin)))
	serialised_header.Write(buf[:n])

	for _, current_vin := range tx.Vin {
		switch current_vin.(type) {
		case Txin_gen:
			serialised_header.WriteByte(TXIN_GEN)
			n = binary.PutUvarint(buf, current_vin.(Txin_gen).Height)
			serialised_header.Write(buf[:n])

		case Txin_to_key:
			serialised_header.WriteByte(TXIN_TO_KEY)
			n = binary.PutUvarint(buf, current_vin.(Txin_to_key).Amount)
			serialised_header.Write(buf[:n])

			// number of Ring member
			n = binary.PutUvarint(buf, uint64(len(current_vin.(Txin_to_key).Key_offsets)))
			serialised_header.Write(buf[:n])

			// write ring members
			for _, offset := range current_vin.(Txin_to_key).Key_offsets {
				n = binary.PutUvarint(buf, offset)
				serialised_header.Write(buf[:n])

			}

			// dump key image, interface needs a concrete type feor accessing array
			cvin := current_vin.(Txin_to_key)
			serialised_header.Write(cvin.K_image[:])

		}
	}

	// time to serialize vouts

	if len(tx.Vout) < 1 {
		panic("No vout")
	}

	n = binary.PutUvarint(buf, uint64(len(tx.Vout)))
	serialised_header.Write(buf[:n])

	for _, current_vout := range tx.Vout {

		// dump amount
		n := binary.PutUvarint(buf, current_vout.Amount)
		serialised_header.Write(buf[:n])

		switch current_vout.Target.(type) {
		case Txout_to_key:

			serialised_header.WriteByte(TXOUT_TO_KEY)

			target := current_vout.Target.(Txout_to_key)
			serialised_header.Write(target.Key[:])

			//serialised_header.Write(current_vout.Target.(Txout_to_key).Key[:])

		default:
			panic("This type of Txout not suppported")

		}
	}

	// dump any extras
	n = binary.PutUvarint(buf, uint64(len(tx.Extra)))
	serialised_header.Write(buf[:n])

	//rlog.Tracef("Extra length %d while serializing\n ", len(tx.Extra))

	serialised_header.Write(tx.Extra[:])

	return serialised_header.Bytes()

}

// serialize entire transaction include signature
func (tx *Transaction) Serialize() []byte {

	header_bytes := tx.SerializeHeader()
	base_bytes := tx.RctSignature.SerializeBase()
	prunable := tx.RctSignature.SerializePrunable()

	buf := append(header_bytes, base_bytes...)
	buf = append(buf, prunable...)

	return buf

}

/*

func (tx *Transaction) IsCoinbase() (result bool){

  // check whether the type is Txin.get

   if len(tx.Vin) != 0 { // coinbase transactions have no vin
    return
   }

   if tx.Vout[0].(Target) != 0 { // coinbase transactions have no vin
    return
   }


}*/
