package main

import (

	"fmt"
	"image"
	"image/color"
	"image/png"
	"os"
)


const (
	Size              = 21
	DataCodewords     = 19
	ECCCodewords      = 7
	MaxBytesByteMode  = 17
	ModeIndicatorByte = 0x4
)


var gfExp [512]int
var gfLog [256]int

func initGFTables() {
	x := 1
	for i := 0; i < 255; i++ {
		gfExp[i] = x
		gfLog[x] = i
		x <<= 1
		if x&0x100 != 0 {
			x ^= 0x11d
		}
	}
	for i := 255; i < 512; i++ {
		gfExp[i] = gfExp[i-255]
	}
	gfLog[0] = -1
}


func gfMul(a, b int) int {
	if a == 0 || b == 0 {
		return 0
	}
	return gfExp[(gfLog[a]+gfLog[b])%255]
}


func rsGenerator(n int) []int {
	g := []int{1}
	for i := 0; i < n; i++ {
		newG := make([]int, len(g)+1)
		for j := range newG {
			newG[j] = 0
		}
		for j := 0; j < len(g); j++ {
			newG[j] ^= g[j]
			newG[j+1] ^= gfMul(g[j], gfExp[i])
		}
		g = newG
	}
	return g
}

func rsComputeECC(data []int, eccLen int, g []int) []int {
	poly := make([]int, len(data)+eccLen)
	copy(poly, data)
	for i := 0; i < len(data); i++ {
		coef := poly[i]
		if coef != 0 {
			for j := 0; j < len(g); j++ {
				poly[i+j] ^= gfMul(coef, g[j])
			}
		}
	}
	return poly[len(data):]
}

type bitBuffer struct {
	bits []bool
}

func (bb *bitBuffer) appendBits(val int, length int) {
	for i := length - 1; i >= 0; i-- {
		bb.bits = append(bb.bits, ((val>>i)&1) == 1)
	}
}

func encodeBytesToCodewords(data []byte) ([]int, error) {
	if len(data) > MaxBytesByteMode {
		return nil, fmt.Errorf("input too long for version 1-L: max %d bytes", MaxBytesByteMode)
	}
	var bb bitBuffer
	bb.appendBits(ModeIndicatorByte, 4)
	bb.appendBits(len(data), 8)
	for _, b := range data {
		bb.appendBits(int(b), 8)
	}
	remainingBits := DataCodewords*8 - len(bb.bits)
	if remainingBits < 0 {
		return nil, fmt.Errorf("data bits exceed capacity")
	}
	if remainingBits >= 4 {
		bb.appendBits(0, 4)
		remainingBits -= 4
	} else {
		bb.appendBits(0, remainingBits)
		remainingBits = 0
	}
	if (len(bb.bits) % 8) != 0 {
		toPad := 8 - (len(bb.bits) % 8)
		bb.appendBits(0, toPad)
		remainingBits -= toPad
	}
	padBytes := []int{0xEC, 0x11}
	idx := 0
	for (len(bb.bits) / 8) < DataCodewords {
		bb.appendBits(padBytes[idx%2], 8)
		idx++
	}
	codewords := make([]int, DataCodewords)
	for i := 0; i < DataCodewords; i++ {
		val := 0
		for b := 0; b < 8; b++ {
			if bb.bits[i*8+b] {
				val |= 1 << (7 - b)
			}
		}
		codewords[i] = val
	}
	return codewords, nil
}

func newMatrix() [][]int {
	m := make([][]int, Size)
	for i := range m {
		m[i] = make([]int, Size)
		for j := range m[i] {
			m[i][j] = -1
		}
	}
	return m
}

func placeFinder(m [][]int, r, c int) {
	p := []string{
		"1111111",
		"1000001",
		"1011101",
		"1011101",
		"1011101",
		"1000001",
		"1111111",
	}
	for i := 0; i < 7; i++ {
		for j := 0; j < 7; j++ {
			if p[i][j] == '1' {
				m[r+i][c+j] = 1
			} else {
				m[r+i][c+j] = 0
			}
		}
	}

	for i := -1; i <= 7; i++ {
		for j := -1; j <= 7; j++ {
			rr := r + i
			cc := c + j
			if rr < 0 || rr >= Size || cc < 0 || cc >= Size {
				continue
			}
			if (i == -1 || i == 7 || j == -1 || j == 7) && m[rr][cc] == -1 {
				m[rr][cc] = 0
			}
		}
	}
}

func placeTiming(m [][]int) {
	for i := 8; i < Size-8; i++ {
		if m[6][i] == -1 {
			if i%2 == 0 {
				m[6][i] = 1
			} else {
				m[6][i] = 0
			}
		}

		if m[i][6] == -1 {
			if i%2 == 0 {
				m[i][6] = 1
			} else {
				m[i][6] = 0
			}
		}
	}
}

func placeData(m [][]int, dataBits []bool) {
	dirUp := true
	row := Size - 1
	col := Size - 1
	bitIdx := 0
	for col > 0 {
		if col == 6 {
			col--
		}
		for {
			for c := col; c >= col-1; c-- {
				if m[row][c] == -1 {
					var bit bool
					if bitIdx < len(dataBits) {
						bit = dataBits[bitIdx]
					} else {
						bit = false
					}
					if bit {
						m[row][c] = 1
					} else {
						m[row][c] = 0
					}
					bitIdx++
				}
			}
			if dirUp {
				row--
			} else {
				row++
			}
			if row < 0 || row >= Size {
				break
			}
		}
		dirUp = !dirUp
		if row < 0 {
			row = 0
		} else {
			row = Size - 1
		}
		col -= 2
	}
}


func applyMask(m [][]int) {
	for r := 0; r < Size; r++ {
		for c := 0; c < Size; c++ {
			if isFunctionModule(r, c) {
				continue
			}
			if (r+c)%2 == 0 {
				// flip
				if m[r][c] == 0 {
					m[r][c] = 1
				} else if m[r][c] == 1 {
					m[r][c] = 0
				}
			}
		}
	}
}

func isFunctionModule(r, c int) bool {
	if (r >= 0 && r <= 8 && c >= 0 && c <= 8) ||
		(r >= 0 && r <= 8 && c >= Size-9 && c <= Size-1) ||
		(r >= Size-9 && r <= Size-1 && c >= 0 && c <= 8) {
		return true
	}
	if r == 6 || c == 6 {
		return true
	}
	formatPos := [][2]int{
		{8, 0}, {8, 1}, {8, 2}, {8, 3}, {8, 4}, {8, 5}, {8, 7}, {8, 8},
		{7, 8}, {5, 8}, {4, 8}, {3, 8}, {2, 8}, {1, 8}, {0, 8},
		{Size - 1, 8}, {Size - 2, 8}, {Size - 3, 8}, {Size - 4, 8}, {Size - 5, 8},
		{8, Size - 1}, {8, Size - 2}, {8, Size - 3}, {8, Size - 4}, {8, Size - 5},
	}
	for _, p := range formatPos {
		if p[0] == r && p[1] == c {
			return true
		}
	}
	return false
}

var formatBits15 = []int{
	1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0,
}

func placeFormatBits(m [][]int) {
	_ = [][2]int{{8, 0}, {8, 1}, {8, 2}, {8, 3}, {8, 4}, {8, 5}, {8, 7}, {8, 8}}
	_ = [][2]int{{7, 8}, {6, 8}, {5, 8}, {4, 8}, {3, 8}, {2, 8}, {1, 8}, {0, 8}}
	_ = [][2]int{{Size - 1, 8}, {Size - 2, 8}, {Size - 3, 8}, {Size - 4, 8}, {Size - 5, 8}, {Size - 6, 8}, {Size - 7, 8}, {Size - 8, 8}}
	_ = [][2]int{{8, Size - 1}, {8, Size - 2}, {8, Size - 3}, {8, Size - 4}, {8, Size - 5}, {8, Size - 6}, {8, Size - 7}, {8, Size - 8}}

	pos := [][2]int{
		{8, 0}, {8, 1}, {8, 2}, {8, 3}, {8, 4}, {8, 5}, {8, 7}, {8, 8},
		{7, 8}, {6, 8}, {5, 8}, {4, 8}, {3, 8}, {2, 8}, {1, 8},
	}
	for i := 0; i < 15; i++ {
		r := pos[i][0]
		c := pos[i][1]
		m[r][c] = formatBits15[i]
	}

	m[Size-1][8] = formatBits15[0]
	m[Size-2][8] = formatBits15[1]
	m[Size-3][8] = formatBits15[2]
	m[Size-4][8] = formatBits15[3]
	m[Size-5][8] = formatBits15[4]
	m[Size-6][8] = formatBits15[5]
	m[Size-7][8] = formatBits15[6]

	m[8][Size-1] = formatBits15[7]
	m[8][Size-2] = formatBits15[8]
	m[8][Size-3] = formatBits15[9]
	m[8][Size-4] = formatBits15[10]
	m[8][Size-5] = formatBits15[11]
	m[8][Size-6] = formatBits15[12]
	m[8][Size-7] = formatBits15[13]
	m[8][Size-8] = formatBits15[14]
}

func bitsFromCodewords(codewords []int, ecc []int) []bool {
	total := append(codewords, ecc...)
	bits := make([]bool, 0, len(total)*8)
	for _, cw := range total {
		for i := 7; i >= 0; i-- {
			bits = append(bits, ((cw>>i)&1) == 1)
		}
	}
	return bits
}

func renderPNG(m [][]int, scale int, filename string) error {
	img := image.NewRGBA(image.Rect(0, 0, Size*scale, Size*scale))
	white := color.RGBA{255, 255, 255, 255}
	black := color.RGBA{0, 0, 0, 255}

	for y := 0; y < Size*scale; y++ {
		for x := 0; x < Size*scale; x++ {
			img.Set(x, y, white)
		}
	}
	for r := 0; r < Size; r++ {
		for c := 0; c < Size; c++ {
			val := m[r][c]
			if val == 1 {
				// draw black
				for y := r * scale; y < (r+1)*scale; y++ {
					for x := c * scale; x < (c+1)*scale; x++ {
						img.Set(x, y, black)
					}
				}
			}
		}
	}
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()
	return png.Encode(f, img)
}

func main() {
	initGFTables()


	payload := []byte("Hello from Ashok")
	
	dataCW, err := encodeBytesToCodewords(payload)
	if err != nil {
		fmt.Println("encode error:", err)
		return
	}


	gen := rsGenerator(ECCCodewords)
	ecc := rsComputeECC(dataCW, ECCCodewords, gen)
	
	mat := newMatrix()
	placeFinder(mat, 0, 0)
	placeFinder(mat, 0, Size-7)
	placeFinder(mat, Size-7, 0)
	placeTiming(mat)
	
	mat[8][Size-8] = 1
	dataBits := bitsFromCodewords(dataCW, ecc)
	placeData(mat, dataBits)
	applyMask(mat)
	placeFormatBits(mat)
	out := "qrcode.png"
	if err := renderPNG(mat, 8, out); err != nil {
		fmt.Println("render error:", err)
		return
	}
	fmt.Println("Written", out)
}
