package main

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"os"
	"runtime"
	"strconv"
	"sync"
	"time"

	"xdx.jelly/xgcl/gmath"
	"xdx.jelly/xgcl/grand"
	"xdx.jelly/xgcl/he/paillier"
	"xdx.jelly/xgcl/sm/sm2"
	"xdx.jelly/xgcl/sm/sm3"
	"xdx.jelly/xgcl/tpc/sm2/sm2m"
	"xdx.jelly/xgcl/tpc/sm2/sm2m/outsource"
)

type timer struct {
	totalTime float64
	last      time.Time
	isStoped  bool
}

func (t *timer) ResetTimer() {
	t.last = time.Now()
	t.totalTime = 0
	t.isStoped = false
}
func (t *timer) StopTimer() {
	if t.isStoped {
		return
	}
	t.totalTime += time.Since(t.last).Seconds()
	t.isStoped = true
}

func (t *timer) StartTimer() {
	if !t.isStoped {
		return
	}
	t.last = time.Now()
	t.isStoped = false
}

func (t *timer) Seconds() float64 {
	t.StopTimer()
	return t.totalTime
}

func testPaillierHomomorphicScalarMul() {
	_, pk, _ := paillier.GenerateKey(2048, grand.Reader)

	limit := big.NewInt(1)
	limit.Lsh(limit, 256)

	m, _ := rand.Int(grand.Reader, limit)
	c, _ := paillier.Encrypt(m, pk, grand.Reader)

	k, _ := rand.Int(grand.Reader, limit)

	var b timer
	b.ResetTimer()

	times := 1000
	for i := 0; i < times; i++ {
		c.HomomorphicScalarMul(c, k, pk)
	}
	b.StopTimer()
	fmt.Printf("HomomorphicScalarMul(single): %.2f per second\n", float64(times)/b.Seconds())
}
func testPaillierHomomorphicScalarAdd() {
	_, pk, _ := paillier.GenerateKey(2048, grand.Reader)

	m1, _ := rand.Int(grand.Reader, pk.N)
	c1, _ := pk.Encrypt(m1, grand.Reader)
	m2, _ := rand.Int(grand.Reader, pk.N)
	c2, _ := pk.Encrypt(m2, grand.Reader)

	var c paillier.Cipher
	times := 1000
	var b timer
	b.ResetTimer()
	for i := 0; i < times; i++ {
		c.HomomorphicAdd(c1, c2, pk)
	}
	b.StopTimer()
	fmt.Printf("HomomorphicAdd(single): %.2f per second\n", float64(times)/b.Seconds())
}
func testPaillierEnc() {
	_, pk, _ := paillier.GenerateKey(2048, grand.Reader)

	m, _ := rand.Int(grand.Reader, pk.N)

	times := 1000
	var b timer
	b.ResetTimer()
	for i := 0; i < times; i++ {
		pk.Encrypt(m, grand.Reader)
	}
	b.StopTimer()
	fmt.Printf("Encrypt(single): %.2f per second\n", float64(times)/b.Seconds())
}

func testOS(N int) float64 {

	// O、客户端生成paillier密钥
	salt := grand.GetRandom(16)
	paiPrivKey, _ := paillier.GenerateKeyFromPassword(2048, []byte("password123"), salt, 1024)
	paiPubKey := paiPrivKey.Public()

	// 一、密钥生成

	// 1）客户端第一步, a1, a2 发外包服务器
	clientKeyGen := &outsource.ClientKeyGenerator{}
	a1, a2, _ := clientKeyGen.Step1(paiPubKey, grand.Reader)

	// 2) 外包服务器第一步
	// 保存encryptedClientKey1，encryptedClientKey2,
	// 把 P 发客户端
	// encryptedClientKey1 -签名用
	// encryptedClientKey2 -解密加密密钥保护结构用
	osKenGen := &outsource.OSKeyGenerator{}
	encryptedClientKey1, _, P, _ := osKenGen.Step1(a1, a2, paiPubKey, grand.Reader)

	// 3) 客户端第二步
	// clientTempKey发协同签名服务端
	clientTempKey, _ := clientKeyGen.Step2(P)

	// 4）协同签名服务器
	// serverTempKey 发客户端，保存serverKey-服务端密钥分量
	serverKey, serverTempKey, publicKey, _ := sm2m.ServerGenSignKey(clientTempKey, grand.GetRandom(32))

	// 5）客户端第三步
	// 把(serverTempKey,S)发送给外包服务器
	S, _ := clientKeyGen.Step3(serverTempKey)

	// 6）外包服务器第二步
	// 把T， osPublicKey发给客户端。保存osPublicKey为用户签名公钥
	T, osPublicKey, _ := osKenGen.Step2(serverTempKey, S)

	// 7）客户端第四步
	// clientPublicKey, 客户端生成的公钥，如果没有返错，则应与ocPublicKey一致。

	clientPublicKey, _ := clientKeyGen.Step4(T)
	clientPublicKey.Equals(osPublicKey)

	e := grand.GetRandom(32)
	var b timer
	// N := 1000
	// 签名
	b.ResetTimer()
	for i := 0; i < N; i++ {
		// 1）客户端发起请求

		// 2）外包方计算
		b.StartTimer()
		outsourcintCtx := new(outsource.OSSignContext)
		PPrime, _ := outsourcintCtx.Step1(grand.Reader) // P'

		// outsourcintCtx把PPrime发给客户端，保存outsourcintCtx.Marshal()

		// 3) 客户端组合数据data = (e,p)=e||px||py并发送给协同服务端
		b.StopTimer()
		data := make([]byte, sm3.Size+2*sm2.ByteSize())
		pos := copy(data, e)
		pos += copy(data[pos:], gmath.BigIntToNByte(PPrime.X, sm2.ByteSize()))
		copy(data[pos:], gmath.BigIntToNByte(PPrime.Y, sm2.ByteSize()))

		// 4) 协同服务端计算,发回data
		b.StartTimer()
		data, _ = sm2m.ServerSign(serverKey, data, grand.Reader)

		// 5) 客户端解析data = r || s1 || s2, 把s1, s2发给外包服务器。
		b.StopTimer()
		r := new(big.Int)
		r.SetBytes(data[:sm2.ByteSize()])
		s1 := new(big.Int).SetBytes(data[sm2.ByteSize() : 2*sm2.ByteSize()])
		s2 := new(big.Int).SetBytes(data[2*sm2.ByteSize():])

		// 6) 外包服务器解析data并计算c,把c发送给客户端
		b.StartTimer()
		c, _ := outsourcintCtx.Step2(s1, s2, encryptedClientKey1, paiPubKey)

		// 7) 客户端计算签名值
		b.StopTimer()
		s, _ := paillier.Decrypt(c, paiPrivKey)
		s.Sub(s, r)
		s.Mod(s, sm2.OrderN())
		sig := &sm2.Signature{
			R: r,
			S: s,
		}
		// 8) 客户端验证签名
		sm2.Verify(e, publicKey, sig)
	}
	return b.Seconds()
	// fmt.Printf("used time: %v\n", b.Seconds())
	// fmt.Printf("Outsource sign: %.2f per second\n", float64(N)/b.Seconds())

}

func multiTestOS(threads int) {
	N := 100

	times := make([]float64, threads)
	var wg sync.WaitGroup
	for i := 0; i < threads; i++ {
		wg.Add(1)
		go func(i int) {
			times[i] = testOS(N)
			wg.Done()
		}(i)
	}
	wg.Wait()
	avg := 0.0
	for _, t := range times {
		avg += t
	}
	avg /= float64(threads)
	fmt.Printf("Outsource sign: %.2f per second\n", float64(N*threads)/avg)
}

func main() {
	testPaillierEnc()
	testPaillierHomomorphicScalarAdd()
	testPaillierHomomorphicScalarMul()
	var threads int
	if len(os.Args) < 2 {
		threads = runtime.NumCPU()
	} else {
		threads, _ = strconv.Atoi(os.Args[1])
	}
	fmt.Println("Cores:", threads)
	multiTestOS(threads)
}
