package main

import (
	"fmt"
	"math/big"
	"syscall/js"

	"github.com/arnaucube/go-blindsecp256k1"

	blindsecp256k1v0 "github.com/arnaucube/go-blindsecp256k1/v0"
)

func main() {
	c := make(chan struct{}, 0)
	println("WASM blindsecp256k1 initialized")
	registerCallbacks()
	<-c
}

func registerCallbacks() {
	js.Global().Set("wasmReady", js.FuncOf(ready))

	// blindv0 & unblindv0 uses:
	// http://www.isecure-journal.com/article_39171_47f9ec605dd3918c2793565ec21fcd7a.pdf
	js.Global().Set("wasmBlindv0", js.FuncOf(blindv0))
	js.Global().Set("wasmUnblindv0", js.FuncOf(unblindv0))
	js.Global().Set("wasmVerifyv0", js.FuncOf(verifyv0))

	// blind & unblind uses: https://sci-hub.do/10.1109/ICCKE.2013.6682844
	js.Global().Set("wasmBlind", js.FuncOf(blind))
	js.Global().Set("wasmUnblind", js.FuncOf(unblind))
	js.Global().Set("wasmVerify", js.FuncOf(verify))
}

func stringToBigInt(s string) *big.Int {
	b, ok := new(big.Int).SetString(s, 10)
	if !ok {
		panic(fmt.Errorf("error parsing string *big.Int: %s", s))
	}
	return b
}

func ready(this js.Value, values []js.Value) interface{} {
	return "ready"
}

func blindv0(this js.Value, values []js.Value) interface{} {
	mStr := values[0].String()
	signerQxStr := values[1].String()
	signerQyStr := values[2].String()
	signerRxStr := values[3].String()
	signerRyStr := values[4].String()

	m := stringToBigInt(mStr)
	signerQx := stringToBigInt(signerQxStr)
	signerQy := stringToBigInt(signerQyStr)
	signerRx := stringToBigInt(signerRxStr)
	signerRy := stringToBigInt(signerRyStr)

	signerQ := &blindsecp256k1.PublicKey{
		X: signerQx,
		Y: signerQy,
	}
	signerR := &blindsecp256k1.Point{
		X: signerRx,
		Y: signerRy,
	}

	mBlinded, user := blindsecp256k1v0.Blind(m, signerQ, signerR)

	r := make(map[string]interface{})
	r["mBlinded"] = mBlinded.String()
	r["uA"] = user.A.String()
	r["uB"] = user.B.String()
	r["uC"] = user.C.String()
	r["uC"] = user.C.String()
	r["uFx"] = user.F.X.String()
	r["uFy"] = user.F.Y.String()
	return r
}

func unblindv0(this js.Value, values []js.Value) interface{} {
	sBlindStr := values[0].String()
	mStr := values[1].String()
	uBStr := values[2].String()
	uCStr := values[3].String()
	uFxStr := values[4].String()
	uFyStr := values[5].String()

	sBlind := stringToBigInt(sBlindStr)
	m := stringToBigInt(mStr)
	uB := stringToBigInt(uBStr)
	uC := stringToBigInt(uCStr)
	uFx := stringToBigInt(uFxStr)
	uFy := stringToBigInt(uFyStr)

	uF := &blindsecp256k1.Point{
		X: uFx,
		Y: uFy,
	}

	u := &blindsecp256k1v0.UserSecretData{
		// A not needed to Unblind
		B: uB,
		C: uC,
		F: uF,
	}

	sig := blindsecp256k1v0.Unblind(sBlind, m, u)

	r := make(map[string]interface{})
	r["s"] = sig.S.String()
	r["fx"] = sig.F.X.String()
	r["fy"] = sig.F.Y.String()
	return r
}

func verifyv0(this js.Value, values []js.Value) interface{} {
	mStr := values[0].String()
	sigSStr := values[1].String()
	sigFxStr := values[2].String()
	sigFyStr := values[3].String()
	qxStr := values[4].String()
	qyStr := values[5].String()

	m := stringToBigInt(mStr)
	sigS := stringToBigInt(sigSStr)
	sigFx := stringToBigInt(sigFxStr)
	sigFy := stringToBigInt(sigFyStr)
	qx := stringToBigInt(qxStr)
	qy := stringToBigInt(qyStr)

	q := &blindsecp256k1.PublicKey{
		X: qx,
		Y: qy,
	}
	sig := &blindsecp256k1.Signature{
		S: sigS,
		F: &blindsecp256k1.Point{
			X: sigFx,
			Y: sigFy,
		},
	}
	verified := blindsecp256k1.Verify(m, sig, q)

	r := verified
	return r
}

func blind(this js.Value, values []js.Value) interface{} {
	mStr := values[0].String()
	signerRxStr := values[1].String()
	signerRyStr := values[2].String()

	m := stringToBigInt(mStr)
	signerRx := stringToBigInt(signerRxStr)
	signerRy := stringToBigInt(signerRyStr)

	signerR := &blindsecp256k1.Point{
		X: signerRx,
		Y: signerRy,
	}

	mBlinded, user := blindsecp256k1.Blind(m, signerR)

	r := make(map[string]interface{})
	r["mBlinded"] = mBlinded.String()
	r["uA"] = user.A.String()
	r["uB"] = user.B.String()
	r["uFx"] = user.F.X.String()
	r["uFy"] = user.F.Y.String()
	return r
}

func unblind(this js.Value, values []js.Value) interface{} {
	sBlindStr := values[0].String()
	mStr := values[1].String()
	uAStr := values[2].String()
	uBStr := values[3].String()
	uFxStr := values[4].String()
	uFyStr := values[5].String()

	sBlind := stringToBigInt(sBlindStr)
	m := stringToBigInt(mStr)
	uA := stringToBigInt(uAStr)
	uB := stringToBigInt(uBStr)
	uFx := stringToBigInt(uFxStr)
	uFy := stringToBigInt(uFyStr)

	uF := &blindsecp256k1.Point{
		X: uFx,
		Y: uFy,
	}

	u := &blindsecp256k1.UserSecretData{
		A: uA,
		B: uB,
		F: uF,
	}

	sig := blindsecp256k1.Unblind(sBlind, m, u)

	r := make(map[string]interface{})
	r["s"] = sig.S.String()
	r["fx"] = sig.F.X.String()
	r["fy"] = sig.F.Y.String()
	return r
}

func verify(this js.Value, values []js.Value) interface{} {
	mStr := values[0].String()
	sigSStr := values[1].String()
	sigFxStr := values[2].String()
	sigFyStr := values[3].String()
	qxStr := values[4].String()
	qyStr := values[5].String()

	m := stringToBigInt(mStr)
	sigS := stringToBigInt(sigSStr)
	sigFx := stringToBigInt(sigFxStr)
	sigFy := stringToBigInt(sigFyStr)
	qx := stringToBigInt(qxStr)
	qy := stringToBigInt(qyStr)

	q := &blindsecp256k1.PublicKey{
		X: qx,
		Y: qy,
	}
	sig := &blindsecp256k1.Signature{
		S: sigS,
		F: &blindsecp256k1.Point{
			X: sigFx,
			Y: sigFy,
		},
	}
	verified := blindsecp256k1.Verify(m, sig, q)

	r := verified
	return r
}
