package utils

import (
	"errors"
	"math/big"

	snark "github.com/arnaucube/go-snark-study"
	"github.com/arnaucube/go-snark-study/circuitcompiler"
	"github.com/arnaucube/go-snark-study/groth16"
)

// []*big.Int
func ArrayBigIntToString(b []*big.Int) []string {
	var o []string
	for i := 0; i < len(b); i++ {
		o = append(o, b[i].String())
	}
	return o
}
func ArrayStringToBigInt(s []string) ([]*big.Int, error) {
	var px []*big.Int
	for i := 0; i < len(s); i++ {
		param, ok := new(big.Int).SetString(s[i], 10)
		if !ok {
			return nil, errors.New("error parsing px from pxString")
		}
		px = append(px, param)
	}
	return px, nil
}

// [3]*big.Int
func String3ToBigInt(s [3]string) ([3]*big.Int, error) {
	var o [3]*big.Int
	for i := 0; i < len(s); i++ {
		param, ok := new(big.Int).SetString(s[i], 10)
		if !ok {
			return o, errors.New("error parsing [3]*big.Int from [3]string")
		}
		o[i] = param
	}
	return o, nil
}
func BigInt3ToString(b [3]*big.Int) [3]string {
	var o [3]string
	o[0] = b[0].String()
	o[1] = b[1].String()
	o[2] = b[2].String()
	return o
}

// [][3]*big.Int
func Array3StringToBigInt(s [][3]string) ([][3]*big.Int, error) {
	var o [][3]*big.Int
	for i := 0; i < len(s); i++ {
		parsed, err := String3ToBigInt(s[i])
		if err != nil {
			return o, err
		}
		o = append(o, parsed)
	}
	return o, nil
}
func Array3BigIntToString(b [][3]*big.Int) [][3]string {
	var o [][3]string
	for i := 0; i < len(b); i++ {
		o = append(o, BigInt3ToString(b[i]))
	}
	return o
}

func String2ToBigInt(s [2]string) ([2]*big.Int, error) {
	var o [2]*big.Int
	for i := 0; i < len(s); i++ {
		param, ok := new(big.Int).SetString(s[i], 10)
		if !ok {
			return o, errors.New("error parsing [2]*big.Int from [2]string")
		}
		o[i] = param
	}
	return o, nil
}

// [3][2]*big.Int
func String32ToBigInt(s [3][2]string) ([3][2]*big.Int, error) {
	var o [3][2]*big.Int
	var err error

	o[0], err = String2ToBigInt(s[0])
	if err != nil {
		return o, err
	}
	o[1], err = String2ToBigInt(s[1])
	if err != nil {
		return o, err
	}
	o[2], err = String2ToBigInt(s[2])
	if err != nil {
		return o, err
	}
	return o, nil
}
func BigInt32ToString(b [3][2]*big.Int) [3][2]string {
	var o [3][2]string
	o[0][0] = b[0][0].String()
	o[0][1] = b[0][1].String()
	o[1][0] = b[1][0].String()
	o[1][1] = b[1][1].String()
	o[2][0] = b[2][0].String()
	o[2][1] = b[2][1].String()
	return o
}

// [][3][2]*big.Int
func Array32StringToBigInt(s [][3][2]string) ([][3][2]*big.Int, error) {
	var o [][3][2]*big.Int
	for i := 0; i < len(s); i++ {
		parsed, err := String32ToBigInt(s[i])
		if err != nil {
			return o, err
		}
		o = append(o, parsed)
	}
	return o, nil
}
func Array32BigIntToString(b [][3][2]*big.Int) [][3][2]string {
	var o [][3][2]string
	for i := 0; i < len(b); i++ {
		o = append(o, BigInt32ToString(b[i]))
	}
	return o
}

// Setup
type SetupString struct {
	// public
	Pk struct {
		G1T [][3]string
		A   [][3]string
		B   [][3][2]string
		C   [][3]string
		Kp  [][3]string
		Ap  [][3]string
		Bp  [][3]string
		Cp  [][3]string
		Z   []string
	}
	Vk struct {
		Vka   [3][2]string
		Vkb   [3]string
		Vkc   [3][2]string
		IC    [][3]string
		G1Kbg [3]string
		G2Kbg [3][2]string
		G2Kg  [3][2]string
		Vkz   [3][2]string
	}
}

func SetupToString(setup snark.Setup) SetupString {
	var s SetupString
	s.Pk.G1T = Array3BigIntToString(setup.Pk.G1T)
	s.Pk.A = Array3BigIntToString(setup.Pk.A)
	s.Pk.B = Array32BigIntToString(setup.Pk.B)
	s.Pk.C = Array3BigIntToString(setup.Pk.C)
	s.Pk.Kp = Array3BigIntToString(setup.Pk.Kp)
	s.Pk.Ap = Array3BigIntToString(setup.Pk.Ap)
	s.Pk.Bp = Array3BigIntToString(setup.Pk.Bp)
	s.Pk.Cp = Array3BigIntToString(setup.Pk.Cp)
	s.Pk.Z = ArrayBigIntToString(setup.Pk.Z)
	s.Vk.Vka = BigInt32ToString(setup.Vk.Vka)
	s.Vk.Vkb = BigInt3ToString(setup.Vk.Vkb)
	s.Vk.Vkc = BigInt32ToString(setup.Vk.Vkc)
	s.Vk.IC = Array3BigIntToString(setup.Vk.IC)
	s.Vk.G1Kbg = BigInt3ToString(setup.Vk.G1Kbg)
	s.Vk.G2Kbg = BigInt32ToString(setup.Vk.G2Kbg)
	s.Vk.G2Kg = BigInt32ToString(setup.Vk.G2Kg)
	s.Vk.Vkz = BigInt32ToString(setup.Vk.Vkz)
	return s
}
func SetupFromString(s SetupString) (snark.Setup, error) {
	var o snark.Setup
	var err error
	o.Pk.G1T, err = Array3StringToBigInt(s.Pk.G1T)
	if err != nil {
		return o, err
	}
	o.Pk.A, err = Array3StringToBigInt(s.Pk.A)
	if err != nil {
		return o, err
	}
	o.Pk.B, err = Array32StringToBigInt(s.Pk.B)
	if err != nil {
		return o, err
	}
	o.Pk.C, err = Array3StringToBigInt(s.Pk.C)
	if err != nil {
		return o, err
	}
	o.Pk.Kp, err = Array3StringToBigInt(s.Pk.Kp)
	if err != nil {
		return o, err
	}
	o.Pk.Ap, err = Array3StringToBigInt(s.Pk.Ap)
	if err != nil {
		return o, err
	}
	o.Pk.Bp, err = Array3StringToBigInt(s.Pk.Bp)
	if err != nil {
		return o, err
	}
	o.Pk.Cp, err = Array3StringToBigInt(s.Pk.Cp)
	if err != nil {
		return o, err
	}
	o.Pk.Z, err = ArrayStringToBigInt(s.Pk.Z)
	if err != nil {
		return o, err
	}

	o.Vk.Vka, err = String32ToBigInt(s.Vk.Vka)
	if err != nil {
		return o, err
	}
	o.Vk.Vkb, err = String3ToBigInt(s.Vk.Vkb)
	if err != nil {
		return o, err
	}
	o.Vk.Vkc, err = String32ToBigInt(s.Vk.Vkc)
	if err != nil {
		return o, err
	}
	o.Vk.IC, err = Array3StringToBigInt(s.Vk.IC)
	if err != nil {
		return o, err
	}
	o.Vk.G1Kbg, err = String3ToBigInt(s.Vk.G1Kbg)
	if err != nil {
		return o, err
	}
	o.Vk.G2Kbg, err = String32ToBigInt(s.Vk.G2Kbg)
	if err != nil {
		return o, err
	}
	o.Vk.G2Kg, err = String32ToBigInt(s.Vk.G2Kg)
	if err != nil {
		return o, err
	}
	o.Vk.Vkz, err = String32ToBigInt(s.Vk.Vkz)
	if err != nil {
		return o, err
	}

	return o, nil

}

// circuit
type CircuitString struct {
	NVars         int
	NPublic       int
	NSignals      int
	PrivateInputs []string
	PublicInputs  []string
	Signals       []string
	Witness       []string
	Constraints   []circuitcompiler.Constraint
	R1CS          struct {
		A [][]string
		B [][]string
		C [][]string
	}
}

func ArrayArrayBigIntToString(b [][]*big.Int) [][]string {
	var o [][]string
	for i := 0; i < len(b); i++ {
		o = append(o, ArrayBigIntToString(b[i]))
	}
	return o
}
func ArrayArrayStringToBigInt(s [][]string) ([][]*big.Int, error) {
	var o [][]*big.Int
	for i := 0; i < len(s); i++ {
		parsed, err := ArrayStringToBigInt(s[i])
		if err != nil {
			return o, err
		}
		o = append(o, parsed)
	}
	return o, nil
}
func CircuitToString(c circuitcompiler.Circuit) CircuitString {
	var cs CircuitString
	cs.NVars = c.NVars
	cs.NPublic = c.NPublic
	cs.NSignals = c.NSignals
	cs.PrivateInputs = c.PrivateInputs
	cs.PublicInputs = c.PublicInputs
	cs.Signals = c.Signals
	cs.Witness = ArrayBigIntToString(c.Witness)
	cs.Constraints = c.Constraints
	cs.R1CS.A = ArrayArrayBigIntToString(c.R1CS.A)
	cs.R1CS.B = ArrayArrayBigIntToString(c.R1CS.B)
	cs.R1CS.C = ArrayArrayBigIntToString(c.R1CS.C)
	return cs
}
func CircuitFromString(cs CircuitString) (circuitcompiler.Circuit, error) {
	var c circuitcompiler.Circuit
	var err error
	c.NVars = cs.NVars
	c.NPublic = cs.NPublic
	c.NSignals = cs.NSignals
	c.PrivateInputs = cs.PrivateInputs
	c.PublicInputs = cs.PublicInputs
	c.Signals = cs.Signals
	c.Witness, err = ArrayStringToBigInt(cs.Witness)
	if err != nil {
		return c, err
	}
	c.Constraints = cs.Constraints
	c.R1CS.A, err = ArrayArrayStringToBigInt(cs.R1CS.A)
	if err != nil {
		return c, err
	}
	c.R1CS.B, err = ArrayArrayStringToBigInt(cs.R1CS.B)
	if err != nil {
		return c, err
	}
	c.R1CS.C, err = ArrayArrayStringToBigInt(cs.R1CS.C)
	if err != nil {
		return c, err
	}
	return c, nil
}

// Proof
type ProofString struct {
	PiA  [3]string
	PiAp [3]string
	PiB  [3][2]string
	PiBp [3]string
	PiC  [3]string
	PiCp [3]string
	PiH  [3]string
	PiKp [3]string
}

func ProofToString(p snark.Proof) ProofString {
	var s ProofString
	s.PiA = BigInt3ToString(p.PiA)
	s.PiAp = BigInt3ToString(p.PiAp)
	s.PiB = BigInt32ToString(p.PiB)
	s.PiBp = BigInt3ToString(p.PiBp)
	s.PiC = BigInt3ToString(p.PiC)
	s.PiCp = BigInt3ToString(p.PiCp)
	s.PiH = BigInt3ToString(p.PiH)
	s.PiKp = BigInt3ToString(p.PiKp)
	return s
}
func ProofFromString(s ProofString) (snark.Proof, error) {
	var p snark.Proof
	var err error

	p.PiA, err = String3ToBigInt(s.PiA)
	if err != nil {
		return p, err
	}
	p.PiAp, err = String3ToBigInt(s.PiAp)
	if err != nil {
		return p, err
	}
	p.PiB, err = String32ToBigInt(s.PiB)
	if err != nil {
		return p, err
	}
	p.PiBp, err = String3ToBigInt(s.PiBp)
	if err != nil {
		return p, err
	}
	p.PiC, err = String3ToBigInt(s.PiC)
	if err != nil {
		return p, err
	}
	p.PiCp, err = String3ToBigInt(s.PiCp)
	if err != nil {
		return p, err
	}
	p.PiH, err = String3ToBigInt(s.PiH)
	if err != nil {
		return p, err
	}
	p.PiKp, err = String3ToBigInt(s.PiKp)
	if err != nil {
		return p, err
	}
	return p, nil
}

// groth
type GrothPkString struct { // Proving Key
	BACDelta [][3]string
	Z        []string
	G1       struct {
		Alpha    [3]string
		Beta     [3]string
		Delta    [3]string
		At       [][3]string
		BACGamma [][3]string
	}
	G2 struct {
		Beta     [3][2]string
		Gamma    [3][2]string
		Delta    [3][2]string
		BACGamma [][3][2]string
	}
	PowersTauDelta [][3]string
}
type GrothVkString struct {
	IC [][3]string
	G1 struct {
		Alpha [3]string
	}
	G2 struct {
		Beta  [3][2]string
		Gamma [3][2]string
		Delta [3][2]string
	}
}
type GrothSetupString struct {
	Pk GrothPkString
	Vk GrothVkString
}

func GrothSetupToString(setup groth16.Setup) GrothSetupString {
	var s GrothSetupString
	s.Pk.BACDelta = Array3BigIntToString(setup.Pk.BACDelta)
	s.Pk.Z = ArrayBigIntToString(setup.Pk.Z)
	s.Pk.G1.Alpha = BigInt3ToString(setup.Pk.G1.Alpha)
	s.Pk.G1.Beta = BigInt3ToString(setup.Pk.G1.Beta)
	s.Pk.G1.Delta = BigInt3ToString(setup.Pk.G1.Delta)
	s.Pk.G1.At = Array3BigIntToString(setup.Pk.G1.At)
	s.Pk.G1.BACGamma = Array3BigIntToString(setup.Pk.G1.BACGamma)
	s.Pk.G2.Beta = BigInt32ToString(setup.Pk.G2.Beta)
	s.Pk.G2.Gamma = BigInt32ToString(setup.Pk.G2.Gamma)
	s.Pk.G2.Delta = BigInt32ToString(setup.Pk.G2.Delta)
	s.Pk.G2.BACGamma = Array32BigIntToString(setup.Pk.G2.BACGamma)
	s.Pk.PowersTauDelta = Array3BigIntToString(setup.Pk.PowersTauDelta)
	s.Vk.IC = Array3BigIntToString(setup.Vk.IC)
	s.Vk.G1.Alpha = BigInt3ToString(setup.Vk.G1.Alpha)
	s.Vk.G2.Beta = BigInt32ToString(setup.Vk.G2.Beta)
	s.Vk.G2.Gamma = BigInt32ToString(setup.Vk.G2.Gamma)
	s.Vk.G2.Delta = BigInt32ToString(setup.Vk.G2.Delta)
	return s
}
func GrothVkFromString(s GrothVkString) (groth16.Vk, error) {
	var vk groth16.Vk
	var err error
	vk.IC, err = Array3StringToBigInt(s.IC)
	if err != nil {
		return vk, err
	}
	vk.G1.Alpha, err = String3ToBigInt(s.G1.Alpha)
	if err != nil {
		return vk, err
	}
	vk.G2.Beta, err = String32ToBigInt(s.G2.Beta)
	if err != nil {
		return vk, err
	}
	vk.G2.Gamma, err = String32ToBigInt(s.G2.Gamma)
	if err != nil {
		return vk, err
	}
	vk.G2.Delta, err = String32ToBigInt(s.G2.Delta)
	if err != nil {
		return vk, err
	}
	return vk, nil
}
func GrothSetupFromString(s GrothSetupString) (groth16.Setup, error) {
	var o groth16.Setup
	var err error
	o.Pk.BACDelta, err = Array3StringToBigInt(s.Pk.BACDelta)
	if err != nil {
		return o, err
	}
	o.Pk.Z, err = ArrayStringToBigInt(s.Pk.Z)
	if err != nil {
		return o, err
	}
	o.Pk.G1.Alpha, err = String3ToBigInt(s.Pk.G1.Alpha)
	if err != nil {
		return o, err
	}
	o.Pk.G1.Beta, err = String3ToBigInt(s.Pk.G1.Beta)
	if err != nil {
		return o, err
	}
	o.Pk.G1.Delta, err = String3ToBigInt(s.Pk.G1.Delta)
	if err != nil {
		return o, err
	}
	o.Pk.G1.At, err = Array3StringToBigInt(s.Pk.G1.At)
	if err != nil {
		return o, err
	}
	o.Pk.G1.BACGamma, err = Array3StringToBigInt(s.Pk.G1.BACGamma)
	if err != nil {
		return o, err
	}
	o.Pk.G2.Beta, err = String32ToBigInt(s.Pk.G2.Beta)
	if err != nil {
		return o, err
	}
	o.Pk.G2.Gamma, err = String32ToBigInt(s.Pk.G2.Gamma)
	if err != nil {
		return o, err
	}
	o.Pk.G2.Delta, err = String32ToBigInt(s.Pk.G2.Delta)
	if err != nil {
		return o, err
	}
	o.Pk.G2.BACGamma, err = Array32StringToBigInt(s.Pk.G2.BACGamma)
	if err != nil {
		return o, err
	}
	o.Pk.PowersTauDelta, err = Array3StringToBigInt(s.Pk.PowersTauDelta)
	if err != nil {
		return o, err
	}
	o.Vk.IC, err = Array3StringToBigInt(s.Vk.IC)
	if err != nil {
		return o, err
	}
	o.Vk.G1.Alpha, err = String3ToBigInt(s.Vk.G1.Alpha)
	if err != nil {
		return o, err
	}
	o.Vk.G2.Beta, err = String32ToBigInt(s.Vk.G2.Beta)
	if err != nil {
		return o, err
	}
	o.Vk.G2.Gamma, err = String32ToBigInt(s.Vk.G2.Gamma)
	if err != nil {
		return o, err
	}
	o.Vk.G2.Delta, err = String32ToBigInt(s.Vk.G2.Delta)
	if err != nil {
		return o, err
	}
	return o, nil
}

type GrothProofString struct {
	PiA [3]string
	PiB [3][2]string
	PiC [3]string
}

func GrothProofToString(p groth16.Proof) GrothProofString {
	var s GrothProofString
	s.PiA = BigInt3ToString(p.PiA)
	s.PiB = BigInt32ToString(p.PiB)
	s.PiC = BigInt3ToString(p.PiC)
	return s
}
func GrothProofFromString(s GrothProofString) (groth16.Proof, error) {
	var p groth16.Proof
	var err error

	p.PiA, err = String3ToBigInt(s.PiA)
	if err != nil {
		return p, err
	}
	p.PiB, err = String32ToBigInt(s.PiB)
	if err != nil {
		return p, err
	}
	p.PiC, err = String3ToBigInt(s.PiC)
	if err != nil {
		return p, err
	}
	return p, nil
}
