package srp

import (
	"bytes"
	"errors"
	"math/big"
)

type SRPClient struct {
	Params     *SRPParams
	Secret1    *big.Int
	Multiplier *big.Int
	A          *big.Int
	X          *big.Int
	M1         []byte
	M2         []byte
	K          []byte
	u          *big.Int
	s          *big.Int
}

func NewClient(params *SRPParams, identity, password, secret1 []byte) *SRPClient {
	multiplier := getMultiplier(params)
	secret1Int := intFromBytes(secret1)
	Ab := getA(params, secret1Int)
	A := intFromBytes(Ab)
	x := getx(params, []byte(""), identity, password) // salt has to be set using SetSalt

	return &SRPClient{
		Params:     params,
		Multiplier: multiplier,
		Secret1:    secret1Int,
		A:          A,
		X:          x,
	}
}

func (c *SRPClient) ComputeA() []byte {
	return intToBytes(c.A)
}

// ComputeVerifier returns a verifier that is calculated as described in
// Section 3 of [SRP-RFC]
func ComputeVerifier(params *SRPParams, salt, identity, password []byte) []byte {
	x := getx(params, salt, identity, password)
	vNum := new(big.Int)
	vNum.Exp(params.G, x, params.N)

	return padToN(vNum, params)
}

func (c *SRPClient) SetB(Bb []byte) {
	B := intFromBytes(Bb)
	u := getu(c.Params, c.A, B)
	S := clientGetS(c.Params, c.Multiplier, c.X, c.Secret1, B, u)

	c.K = getK(c.Params, S)
	c.M1 = getM1(c.Params, intToBytes(c.A), Bb, c.K) // modified S -> c.K
	c.M2 = getM2(c.Params, intToBytes(c.A), c.M1, c.K)

	c.u = u               // Only for tests
	c.s = intFromBytes(S) // Only for tests
}

func (c *SRPClient) SetSalt(salt, identity, password []byte) {
	c.X = getx(c.Params, salt, identity, password) //Overwrite
}

func (c *SRPClient) ComputeM1() []byte {
	if c.M1 == nil {
		panic("Incomplete protocol")
	}

	return c.M1
}

func (c *SRPClient) ComputeK() []byte {
	return c.K
}

func (c *SRPClient) CheckM2(M2 []byte) error {
	if !bytes.Equal(c.M2, M2) {
		return errors.New("M2 didn't check")
	} else {
		return nil
	}
}

func getA(params *SRPParams, a *big.Int) []byte {
	ANum := new(big.Int)
	ANum.Exp(params.G, a, params.N)
	return padToN(ANum, params)
}

func clientGetS(params *SRPParams, k, x, a, B, u *big.Int) []byte {
	BLessThan0 := B.Cmp(big.NewInt(0)) <= 0
	NLessThanB := params.N.Cmp(B) <= 0
	if BLessThan0 || NLessThanB {
		panic("invalid server-supplied 'B', must be 1..N-1")
	}

	result1 := new(big.Int)
	result1.Exp(params.G, x, params.N)

	result2 := new(big.Int)
	result2.Mul(k, result1)

	result3 := new(big.Int)
	result3.Sub(B, result2)

	result4 := new(big.Int)
	result4.Mul(u, x)

	result5 := new(big.Int)
	result5.Add(a, result4)

	result6 := new(big.Int)
	result6.Exp(result3, result5, params.N)

	result7 := new(big.Int)
	result7.Mod(result6, params.N)

	return padToN(result7, params)
}

func getx(params *SRPParams, salt, I, P []byte) *big.Int {
	var ipBytes []byte
	ipBytes = append(ipBytes, I...)
	ipBytes = append(ipBytes, []byte(":")...)
	ipBytes = append(ipBytes, P...)

	hashIP := params.Hash.New()
	hashIP.Write(ipBytes)

	hashX := params.Hash.New()
	hashX.Write(salt)
	hashX.Write(hashToBytes(hashIP))

	return hashToInt(hashX)
}
