package experiment

// ffe stands for finite field elements
type ffe struct {
	v [8]byte
}

func NewFFE(in byte) *ffe {
	r := new(ffe)
	for i := 0; i < 8; i++ {
		r.v[i] = (in >> i) & 1
	}
	return r
}

type gf256 struct {
	f           [9]byte
	invertTable [256]byte
}

var sm4Field = &gf256{
	f: [9]byte{1, 0, 1, 0, 1, 1, 1, 1, 1},
}

var aesField = &gf256{
	f: [9]byte{1, 1, 0, 1, 1, 0, 0, 0, 1},
}

func (g *gf256) _add(u, v []byte) []byte {
	l := len(u)
	if l < len(v) {
		l = len(v)
	}
	r := make([]byte, l)
	copy(r, u)
	for i := 0; i < len(v); i++ {
		r[i] = r[i] ^ v[i]
	}
	return r
}

func (g *gf256) Add(u, v *ffe) *ffe {
	r := new(ffe)
	for i := 0; i < 8; i++ {
		r.v[i] = u.v[i] ^ v.v[i]
	}
	return r
}

// return v*x^n
func (g *gf256) _mulxn(v []byte, n int) []byte {
	buf := make([]byte, n, len(v)+n)
	buf = append(buf, v[:]...)
	return buf
}

func (g *gf256) reduce(v []byte) *ffe {
	buf := append([]byte{}, v...)
	for i := len(buf) - 1; i >= 8; i-- {
		if buf[i] == 1 {
			buf = g._add(buf, g._mulxn(g.f[:], i-8))
		}
	}

	r := new(ffe)
	copy(r.v[:], buf)
	return r
}

// vmul return v1*v2 mod m
func (g *gf256) Mul(u, v *ffe) *ffe {
	r := make([]byte, 16)
	// copy(r, u.v[:])
	for i := 0; i < 8; i++ {
		if v.v[i] == 1 {
			r = g._add(r, g._mulxn(u.v[:], i))
		}
	}

	return g.reduce(r)
}

func (g *gf256) IsOne(v *ffe) bool {
	if v.v[0] == 0 {
		return false
	}
	for i := 1; i < len(v.v); i++ {
		if v.v[i] != 0 {
			return false
		}
	}
	return true
}

func (g *gf256) makeInverseTable() {
	g.invertTable[0] = 0
	for i := 1; i < 256; i++ {
		u := NewFFE(byte(i))
		for j := 1; j < 256; j++ {
			if g.IsOne(g.Mul(u, NewFFE(byte(j)))) {
				g.invertTable[i] = byte(j)
				break
			}
		}
	}
}
func init() {
	aesField.makeInverseTable()
	sm4Field.makeInverseTable()

}

func (g *gf256) Invert(v *ffe) *ffe {
	return NewFFE(g.invertTable[v.Byte()])
}

func (e *ffe) Byte() byte {
	r := byte(0)
	for i := 0; i < len(e.v); i++ {
		r = r << 1
		r = r | e.v[len(e.v)-1-i]
	}
	return r
}
