package moneroutil

import (
	"fmt"
	"io"
)

const (
	txInGenMarker    = 0xff
	txInToKeyMarker  = 2
	txOutToKeyMarker = 2
)

var UnimplementedError = fmt.Errorf("Unimplemented")

type txOutToKey struct {
	key Key
}

type TxOutTargetSerializer interface {
	TargetSerialize() []byte
	String() string
}

type txInGen struct {
	height uint64
}

type txInToKey struct {
	amount     uint64
	keyOffsets []uint64
	keyImage   Key
}

type TxInSerializer interface {
	TxInSerialize() []byte
	MixinLen() int
}

type TxOut struct {
	amount uint64
	target TxOutTargetSerializer
}

type TransactionPrefix struct {
	version    uint32
	unlockTime uint64
	vin        []TxInSerializer
	vout       []*TxOut
	extra      []byte
}

type Transaction struct {
	TransactionPrefix
	signatures   []RingSignature
	rctSignature *RctSig
}

func (h *Hash) Serialize() (result []byte) {
	result = h[:]
	return
}

func (p *Key) Serialize() (result []byte) {
	result = p[:]
	return
}

func (t *txOutToKey) TargetSerialize() (result []byte) {
	result = append([]byte{txOutToKeyMarker}, t.key.Serialize()...)
	return
}

func (t *txOutToKey) String() (result string) {
	result = fmt.Sprintf("key: %x", t.key)
	return
}

func (t *txInGen) TxInSerialize() (result []byte) {
	result = append([]byte{txInGenMarker}, Uint64ToBytes(t.height)...)
	return
}

func (t *txInGen) MixinLen() int {
	return 0
}

func (t *txInToKey) TxInSerialize() (result []byte) {
	result = append([]byte{txInToKeyMarker}, Uint64ToBytes(t.amount)...)
	result = append(result, Uint64ToBytes(uint64(len(t.keyOffsets)))...)
	for _, keyOffset := range t.keyOffsets {
		result = append(result, Uint64ToBytes(keyOffset)...)
	}
	result = append(result, t.keyImage[:]...)
	return
}

func (t *txInToKey) MixinLen() int {
	return len(t.keyOffsets)
}

func (t *TxOut) Serialize() (result []byte) {
	result = append(Uint64ToBytes(t.amount), t.target.TargetSerialize()...)
	return
}

func (t *TransactionPrefix) SerializePrefix() (result []byte) {
	result = append(Uint64ToBytes(uint64(t.version)), Uint64ToBytes(t.unlockTime)...)
	result = append(result, Uint64ToBytes(uint64(len(t.vin)))...)
	for _, txIn := range t.vin {
		result = append(result, txIn.TxInSerialize()...)
	}
	result = append(result, Uint64ToBytes(uint64(len(t.vout)))...)
	for _, txOut := range t.vout {
		result = append(result, txOut.Serialize()...)
	}
	result = append(result, Uint64ToBytes(uint64(len(t.extra)))...)
	result = append(result, t.extra...)
	return
}

func (t *TransactionPrefix) PrefixHash() (hash Hash) {
	hash = Keccak256(t.SerializePrefix())
	return
}

func (t *TransactionPrefix) OutputSum() (sum uint64) {
	for _, output := range t.vout {
		sum += output.amount
	}
	return
}

func (t *Transaction) Serialize() (result []byte) {
	result = t.SerializePrefix()
	if t.version == 1 {
		for i := 0; i < len(t.signatures); i++ {
			result = append(result, t.signatures[i].Serialize()...)
		}
	} else {
		result = append(result, t.rctSignature.SerializeBase()...)
		result = append(result, t.rctSignature.SerializePrunable()...)
	}
	return
}

func (t *Transaction) SerializeBase() (result []byte) {
	if t.version == 1 {
		result = t.Serialize()
	} else {
		result = append(t.SerializePrefix(), t.rctSignature.SerializeBase()...)
	}
	return
}

func (t *Transaction) GetHash() (result Hash) {
	if t.version == 1 {
		result = Keccak256(t.Serialize())
	} else {
		// version 2 requires first computing 3 separate hashes
		// prefix, rctBase and rctPrunable
		// and then hashing the hashes together to get the final hash
		prefixHash := t.PrefixHash()
		rctBaseHash := t.rctSignature.BaseHash()
		rctPrunableHash := t.rctSignature.PrunableHash()
		result = Keccak256(prefixHash[:], rctBaseHash[:], rctPrunableHash[:])
	}
	return
}

func ParseTxInGen(buf io.Reader) (txIn *txInGen, err error) {
	t := new(txInGen)
	t.height, err = ReadVarInt(buf)
	if err != nil {
		return
	}
	txIn = t
	return
}

func ParseTxInToKey(buf io.Reader) (txIn *txInToKey, err error) {
	t := new(txInToKey)
	t.amount, err = ReadVarInt(buf)
	if err != nil {
		return
	}
	keyOffsetLen, err := ReadVarInt(buf)
	if err != nil {
		return
	}
	t.keyOffsets = make([]uint64, keyOffsetLen, keyOffsetLen)
	for i := 0; i < int(keyOffsetLen); i++ {
		t.keyOffsets[i], err = ReadVarInt(buf)
		if err != nil {
			return
		}
	}
	pubKey := make([]byte, KeyLength)
	n, err := buf.Read(pubKey)
	if err != nil {
		return
	}
	if n != KeyLength {
		err = fmt.Errorf("Buffer not long enough for public key")
		return
	}
	copy(t.keyImage[:], pubKey)
	txIn = t
	return
}

func ParseTxIn(buf io.Reader) (txIn TxInSerializer, err error) {
	marker := make([]byte, 1)
	n, err := buf.Read(marker)
	if n != 1 {
		err = fmt.Errorf("Buffer not enough for TxIn")
		return
	}
	if err != nil {
		return
	}
	switch {
	case marker[0] == txInGenMarker:
		txIn, err = ParseTxInGen(buf)
	case marker[0] == txInToKeyMarker:
		txIn, err = ParseTxInToKey(buf)
	}
	return
}

func ParseTxOutToKey(buf io.Reader) (txOutTarget *txOutToKey, err error) {
	t := new(txOutToKey)
	pubKey := make([]byte, KeyLength)
	n, err := buf.Read(pubKey)
	if err != nil {
		return
	}
	if n != KeyLength {
		err = fmt.Errorf("Buffer not long enough for public key")
		return
	}
	copy(t.key[:], pubKey)
	txOutTarget = t
	return
}

func ParseTxOut(buf io.Reader) (txOut *TxOut, err error) {
	t := new(TxOut)
	t.amount, err = ReadVarInt(buf)
	if err != nil {
		return
	}
	marker := make([]byte, 1)
	n, err := buf.Read(marker)
	if err != nil {
		return
	}
	if n != 1 {
		err = fmt.Errorf("Buffer not long enough for TxOut")
		return
	}
	switch {
	case marker[0] == txOutToKeyMarker:
		t.target, err = ParseTxOutToKey(buf)
	default:
		err = fmt.Errorf("Bad Marker")
		return
	}
	if err != nil {
		return
	}
	txOut = t
	return
}

func ParseExtra(buf io.Reader) (extra []byte, err error) {
	length, err := ReadVarInt(buf)
	if err != nil {
		return
	}
	e := make([]byte, int(length))
	n, err := buf.Read(e)
	if err != nil {
		return
	}
	if n != int(length) {
		err = fmt.Errorf("Not enough bytes for extra")
		return
	}
	extra = e
	return
}

func ParseTransaction(buf io.Reader) (transaction *Transaction, err error) {
	t := new(Transaction)
	version, err := ReadVarInt(buf)
	if err != nil {
		return
	}
	t.version = uint32(version)
	t.unlockTime, err = ReadVarInt(buf)
	if err != nil {
		return
	}
	numInputs, err := ReadVarInt(buf)
	if err != nil {
		return
	}
	var mixinLengths []int
	t.vin = make([]TxInSerializer, int(numInputs), int(numInputs))
	for i := 0; i < int(numInputs); i++ {
		t.vin[i], err = ParseTxIn(buf)
		if err != nil {
			return
		}
		mixinLen := t.vin[i].MixinLen()
		if mixinLen > 0 {
			mixinLengths = append(mixinLengths, mixinLen)
		}
	}
	numOutputs, err := ReadVarInt(buf)
	if err != nil {
		return
	}
	t.vout = make([]*TxOut, int(numOutputs), int(numOutputs))
	for i := 0; i < int(numOutputs); i++ {
		t.vout[i], err = ParseTxOut(buf)
		if err != nil {
			return
		}
	}
	t.extra, err = ParseExtra(buf)
	if err != nil {
		return
	}
	if t.version == 1 {
		t.signatures, err = ParseSignatures(mixinLengths, buf)
		if err != nil {
			return
		}
	} else {
		var nMixins int
		if len(mixinLengths) > 0 {
			nMixins = mixinLengths[0] - 1
		}
		t.rctSignature, err = ParseRingCtSignature(buf, int(numInputs), int(numOutputs), nMixins)
		if err != nil {
			return
		}
	}
	transaction = t
	return
}
