package moneroutil

import (
	"fmt"
	"io"
)

const (
	RCTTypeNull = iota
	RCTTypeFull
	RCTTypeSimple
)

// V = Vector, M = Matrix
type KeyV []Key
type KeyM []KeyV

// Confidential Transaction Keys
type CtKey struct {
	destination Key
	mask        Key
}

// V = Vector, M = Matrix
type CtKeyV []CtKey
type CtKeyM []CtKeyV

// Pedersen Commitment is generated from this struct
// C = aG + bH where a = mask and b = amount
// senderPk is the one-time public key for ECDH exchange
type ecdhTuple struct {
	mask     Key
	amount   Key
	senderPk Key
}

type Key64 [64]Key

// Borromean Signature
type BoroSig struct {
	s0 Key64
	s1 Key64
	ee Key
}

// MLSAG (Multilayered Linkable Spontaneous Anonymous Group) Signature
type MgSig struct {
	ss KeyM
	cc Key
	ii KeyV
}

// Range Signature
// Essentially data for a Borromean Signature
type RangeSig struct {
	asig BoroSig
	ci   Key64
}

// Ring Confidential Signature
type RctSigBase struct {
	sigType    uint8
	message    Key
	mixRing    CtKeyM
	pseudoOuts KeyV
	ecdhInfo   []ecdhTuple
	outPk      CtKeyV
	txFee      uint64
}

type RctSigPrunable struct {
	rangeSigs []RangeSig
	mgSigs    []MgSig
}

type RctSig struct {
	RctSigBase
	RctSigPrunable
}

func (k *Key) ToExtended() (result *ExtendedGroupElement) {
	result = new(ExtendedGroupElement)
	result.FromBytes(k)
	return
}

func identity() (result *Key) {
	result = new(Key)
	result[0] = 1
	return
}

// convert a uint64 to a scalar
func d2h(val uint64) (result *Key) {
	result = new(Key)
	for i := 0; val > 0; i++ {
		result[i] = byte(val & 0xFF)
		val /= 256
	}
	return
}

// multiply a scalar by H (second curve point of Pedersen Commitment)
func ScalarMultH(scalar *Key) (result *Key) {
	h := new(ExtendedGroupElement)
	h.FromBytes(&H)
	resultPoint := new(ProjectiveGroupElement)
	GeScalarMult(resultPoint, scalar, h)
	result = new(Key)
	resultPoint.ToBytes(result)
	return
}

// add two points together
func AddKeys(sum, k1, k2 *Key) {
	a := k1.ToExtended()
	b := new(CachedGroupElement)
	k2.ToExtended().ToCached(b)
	c := new(CompletedGroupElement)
	geAdd(c, a, b)
	tmp := new(ExtendedGroupElement)
	c.ToExtended(tmp)
	tmp.ToBytes(sum)
	return
}

// compute a*G + b*B
func AddKeys2(result, a, b, B *Key) {
	BPoint := B.ToExtended()
	RPoint := new(ProjectiveGroupElement)
	GeDoubleScalarMultVartime(RPoint, b, BPoint, a)
	RPoint.ToBytes(result)
	return
}

// subtract two points together
func SubKeys(diff, k1, k2 *Key) {
	a := k1.ToExtended()
	b := new(CachedGroupElement)
	k2.ToExtended().ToCached(b)
	c := new(CompletedGroupElement)
	geSub(c, a, b)
	tmp := new(ExtendedGroupElement)
	c.ToExtended(tmp)
	tmp.ToBytes(diff)
	return
}

func (k *Key64) Serialize() (result []byte) {
	for _, key := range k {
		result = append(result, key[:]...)
	}
	return
}

func (b *BoroSig) Serialize() (result []byte) {
	result = append(b.s0.Serialize(), b.s1.Serialize()...)
	result = append(result, b.ee[:]...)
	return
}

func (r *RangeSig) Serialize() (result []byte) {
	result = append(r.asig.Serialize(), r.ci.Serialize()...)
	return
}

func (m *MgSig) Serialize() (result []byte) {
	for i := 0; i < len(m.ss); i++ {
		for j := 0; j < len(m.ss[i]); j++ {
			result = append(result, m.ss[i][j][:]...)
		}
	}
	result = append(result, m.cc[:]...)
	return
}

func (r *RctSigBase) SerializeBase() (result []byte) {
	result = []byte{r.sigType}
	// Null type returns right away
	if r.sigType == RCTTypeNull {
		return
	}
	result = append(result, Uint64ToBytes(r.txFee)...)
	if r.sigType == RCTTypeSimple {
		for _, input := range r.pseudoOuts {
			result = append(result, input[:]...)
		}
	}
	for _, ecdh := range r.ecdhInfo {
		result = append(result, ecdh.mask[:]...)
		result = append(result, ecdh.amount[:]...)
	}
	for _, ctKey := range r.outPk {
		result = append(result, ctKey.mask[:]...)
	}
	return
}

func (r *RctSigBase) BaseHash() (result Hash) {
	result = Keccak256(r.SerializeBase())
	return
}

func (r *RctSig) SerializePrunable() (result []byte) {
	if r.sigType == RCTTypeNull {
		return
	}
	for _, rangeSig := range r.rangeSigs {
		result = append(result, rangeSig.Serialize()...)
	}
	for _, mgSig := range r.mgSigs {
		result = append(result, mgSig.Serialize()...)
	}
	return
}

func (r *RctSig) PrunableHash() (result Hash) {
	if r.sigType == RCTTypeNull {
		return
	}
	result = Keccak256(r.SerializePrunable())
	return
}

func verBorromean(b *BoroSig, p1, p2 *Key64) bool {
	var data []byte
	tmp, tmp2 := new(Key), new(Key)
	for i := 0; i < 64; i++ {
		AddKeys2(tmp, &b.s0[i], &b.ee, &p1[i])
		tmp3 := HashToScalar(tmp[:])
		AddKeys2(tmp2, &b.s1[i], tmp3, &p2[i])
		data = append(data, tmp2[:]...)
	}
	computed := HashToScalar(data)
	return *computed == b.ee
}

func verRange(c *Key, as RangeSig) bool {
	var CiH Key64
	tmp := identity()
	for i := 0; i < 64; i++ {
		SubKeys(&CiH[i], &as.ci[i], &H2[i])
		AddKeys(tmp, tmp, &as.ci[i])
	}
	if *c != *tmp {
		return false
	}
	return verBorromean(&as.asig, &as.ci, &CiH)
}

// Verify a RCTTypeSimple RingCT Signature
func (r *RctSig) VerifyRctSimple() bool {
	sumOutPks := identity()
	for _, ctKey := range r.outPk {
		AddKeys(sumOutPks, sumOutPks, &ctKey.mask)
	}
	txFeeKey := ScalarMultH(d2h(r.txFee))
	AddKeys(sumOutPks, sumOutPks, txFeeKey)
	sumPseudoOuts := identity()
	for _, pseudoOut := range r.pseudoOuts {
		AddKeys(sumPseudoOuts, sumPseudoOuts, &pseudoOut)
	}
	if *sumPseudoOuts != *sumOutPks {
		return false
	}
	for i, ctKey := range r.outPk {
		if !verRange(&ctKey.mask, r.rangeSigs[i]) {
			return false
		}
	}
	return true
}

func ParseKey(buf io.Reader) (result Key, err error) {
	key := make([]byte, KeyLength)
	if _, err = buf.Read(key); err != nil {
		return
	}
	copy(result[:], key)
	return
}

func ParseCtKey(buf io.Reader) (result CtKey, err error) {
	if result.mask, err = ParseKey(buf); err != nil {
		return
	}
	return
}

func ParseKey64(buf io.Reader) (result Key64, err error) {
	for i := 0; i < 64; i++ {
		if result[i], err = ParseKey(buf); err != nil {
			return
		}
	}
	return
}

func ParseBoroSig(buf io.Reader) (result BoroSig, err error) {
	if result.s0, err = ParseKey64(buf); err != nil {
		return
	}
	if result.s1, err = ParseKey64(buf); err != nil {
		return
	}
	if result.ee, err = ParseKey(buf); err != nil {
		return
	}
	return
}

func ParseRangeSig(buf io.Reader) (result RangeSig, err error) {
	if result.asig, err = ParseBoroSig(buf); err != nil {
		return
	}
	if result.ci, err = ParseKey64(buf); err != nil {
		return
	}
	return
}

func ParseRingCtSignature(buf io.Reader, nInputs, nOutputs, nMixin int) (result *RctSig, err error) {
	r := new(RctSig)
	sigType := make([]byte, 1)
	_, err = buf.Read(sigType)
	if err != nil {
		return
	}
	r.sigType = uint8(sigType[0])
	if r.sigType == RCTTypeNull {
		result = r
		return
	}
	if r.sigType != RCTTypeFull || r.sigType != RCTTypeSimple {
		err = fmt.Errorf("Bad sigType %d", r.sigType)
	}
	r.txFee, err = ReadVarInt(buf)
	if err != nil {
		return
	}
	var nMg, nSS int
	if r.sigType == RCTTypeSimple {
		nMg = nInputs
		nSS = 2
		r.pseudoOuts = make([]Key, nInputs)
		for i := 0; i < nInputs; i++ {
			if r.pseudoOuts[i], err = ParseKey(buf); err != nil {
				return
			}
		}
	} else {
		nMg = 1
		nSS = nInputs + 1
	}
	r.ecdhInfo = make([]ecdhTuple, nOutputs)
	for i := 0; i < nOutputs; i++ {
		if r.ecdhInfo[i].mask, err = ParseKey(buf); err != nil {
			return
		}
		if r.ecdhInfo[i].amount, err = ParseKey(buf); err != nil {
			return
		}
	}
	r.outPk = make([]CtKey, nOutputs)
	for i := 0; i < nOutputs; i++ {
		if r.outPk[i], err = ParseCtKey(buf); err != nil {
			return
		}
	}
	r.rangeSigs = make([]RangeSig, nOutputs)
	for i := 0; i < nOutputs; i++ {
		if r.rangeSigs[i], err = ParseRangeSig(buf); err != nil {
			return
		}
	}
	r.mgSigs = make([]MgSig, nMg)
	for i := 0; i < nMg; i++ {
		r.mgSigs[i].ss = make([]KeyV, nMixin+1)
		for j := 0; j < nMixin+1; j++ {
			r.mgSigs[i].ss[j] = make([]Key, nSS)
			for k := 0; k < nSS; k++ {
				if r.mgSigs[i].ss[j][k], err = ParseKey(buf); err != nil {
					return
				}
			}
		}
		if r.mgSigs[i].cc, err = ParseKey(buf); err != nil {
			return
		}
	}
	result = r
	return
}
