package main

// QR Code encoder, written from ISO/IEC 18004 for this program.
//
// Scope: byte mode (8-bit), versions 1 to 10, all four error-correction
// levels. That is up to 271 bytes of payload at level L and 154 at level M,
// which is far more than the "http://192.168.1.20:53317/?c=123456" style URL
// SnapBeam needs to print. Numeric, alphanumeric and kanji modes, ECI, and
// versions 11 to 40 are deliberately not implemented; encodeQR reports a plain
// error instead of silently truncating.
//
// The pieces that are easy to get subtly wrong, and are therefore pinned by
// tests against the standard's own worked example, are: the BCH(15,5) format
// information with its 0x5412 mask, the BCH(18,6) version information, the
// Reed-Solomon codewords over GF(256) with primitive polynomial 0x11D, and the
// penalty-driven choice of one of the eight mask patterns.

import (
	"errors"
	"fmt"
	"strings"
)

// ecLevel is the error-correction level. The values are the two-bit codes the
// format information uses, NOT the L/M/Q/H ordering, because that ordering
// (01, 00, 11, 10) is the one thing about it nobody remembers correctly.
type ecLevel int

const (
	ecLow      ecLevel = iota // L, ~7% recovery, format bits 01
	ecMedium                  // M, ~15% recovery, format bits 00
	ecQuartile                // Q, ~25% recovery, format bits 11
	ecHigh                    // H, ~30% recovery, format bits 10
)

func (e ecLevel) String() string {
	switch e {
	case ecLow:
		return "L"
	case ecMedium:
		return "M"
	case ecQuartile:
		return "Q"
	case ecHigh:
		return "H"
	}
	return "?"
}

// formatCode is the two-bit error-correction indicator used by the format
// information. It is not the same as the enum order.
func (e ecLevel) formatCode() int {
	switch e {
	case ecLow:
		return 1
	case ecMedium:
		return 0
	case ecQuartile:
		return 3
	case ecHigh:
		return 2
	}
	return 0
}

// ---------------------------------------------------------------------------
// Version tables (ISO/IEC 18004 tables 1, 9 and E.1), versions 1..10 only
// ---------------------------------------------------------------------------

// totalCodewords[v] is the total number of 8-bit codewords in version v,
// data and error correction together.
var totalCodewords = map[int]int{
	1: 26, 2: 44, 3: 70, 4: 100, 5: 134,
	6: 172, 7: 196, 8: 242, 9: 292, 10: 346,
}

// blockSpec describes how one version/level splits its codewords into blocks.
// Group 2 blocks always hold exactly one more data codeword than group 1.
type blockSpec struct {
	ecPerBlock int // error-correction codewords in every block
	numG1      int // number of blocks in group 1
	dataG1     int // data codewords per group-1 block
	numG2      int // number of blocks in group 2 (may be 0)
	dataG2     int // data codewords per group-2 block
}

func (b blockSpec) dataCodewords() int { return b.numG1*b.dataG1 + b.numG2*b.dataG2 }
func (b blockSpec) blocks() int        { return b.numG1 + b.numG2 }

// ecBlocks is indexed [version][level]. Every row is checked against
// totalCodewords by TestQRBlockTablesAreSelfConsistent, which is the only
// practical defence against a typo in a table this shape.
var ecBlocks = map[int][4]blockSpec{
	1: {
		{7, 1, 19, 0, 0}, {10, 1, 16, 0, 0}, {13, 1, 13, 0, 0}, {17, 1, 9, 0, 0},
	},
	2: {
		{10, 1, 34, 0, 0}, {16, 1, 28, 0, 0}, {22, 1, 22, 0, 0}, {28, 1, 16, 0, 0},
	},
	3: {
		{15, 1, 55, 0, 0}, {26, 1, 44, 0, 0}, {18, 2, 17, 0, 0}, {22, 2, 13, 0, 0},
	},
	4: {
		{20, 1, 80, 0, 0}, {18, 2, 32, 0, 0}, {26, 2, 24, 0, 0}, {16, 4, 9, 0, 0},
	},
	5: {
		{26, 1, 108, 0, 0}, {24, 2, 43, 0, 0}, {18, 2, 15, 2, 16}, {22, 2, 11, 2, 12},
	},
	6: {
		{18, 2, 68, 0, 0}, {16, 4, 27, 0, 0}, {24, 4, 19, 0, 0}, {28, 4, 15, 0, 0},
	},
	7: {
		{20, 2, 78, 0, 0}, {18, 4, 31, 0, 0}, {18, 2, 14, 4, 15}, {26, 4, 13, 1, 14},
	},
	8: {
		{24, 2, 97, 0, 0}, {22, 2, 38, 2, 39}, {22, 4, 18, 2, 19}, {26, 4, 14, 2, 15},
	},
	9: {
		{30, 2, 116, 0, 0}, {22, 3, 36, 2, 37}, {20, 4, 16, 4, 17}, {24, 4, 12, 4, 13},
	},
	10: {
		{18, 2, 68, 2, 69}, {26, 4, 43, 1, 44}, {24, 6, 19, 2, 20}, {28, 6, 15, 2, 16},
	},
}

// alignmentCenters[v] lists the row/column coordinates of alignment pattern
// centres. Every combination is used except the three that would sit on top of
// a finder pattern.
var alignmentCenters = map[int][]int{
	1:  {},
	2:  {6, 18},
	3:  {6, 22},
	4:  {6, 26},
	5:  {6, 30},
	6:  {6, 34},
	7:  {6, 22, 38},
	8:  {6, 24, 42},
	9:  {6, 26, 46},
	10: {6, 28, 50},
}

// remainderBits[v] is the number of zero bits appended after the interleaved
// codewords so the data exactly fills the symbol.
var remainderBits = map[int]int{
	1: 0, 2: 7, 3: 7, 4: 7, 5: 7, 6: 7, 7: 0, 8: 0, 9: 0, 10: 0,
}

const maxQRVersion = 10

// ---------------------------------------------------------------------------
// GF(256) arithmetic and Reed-Solomon
// ---------------------------------------------------------------------------

// The field is GF(2^8) with primitive polynomial x^8+x^4+x^3+x^2+1 (0x11D),
// which is the one QR uses. gfExp is doubled in length so a product of two
// logarithms can be looked up without a modulo.
var (
	gfExp [512]byte
	gfLog [256]byte
)

func init() {
	x := 1
	for i := 0; i < 255; i++ {
		gfExp[i] = byte(x)
		gfLog[x] = byte(i)
		x <<= 1
		if x&0x100 != 0 {
			x ^= 0x11D
		}
	}
	for i := 255; i < 512; i++ {
		gfExp[i] = gfExp[i-255]
	}
}

func gfMul(a, b byte) byte {
	if a == 0 || b == 0 {
		return 0
	}
	return gfExp[int(gfLog[a])+int(gfLog[b])]
}

// rsGenerator returns the generator polynomial of degree n, the product of
// (x - alpha^i) for i in [0, n), with the highest-order coefficient first.
func rsGenerator(n int) []byte {
	g := []byte{1}
	for i := 0; i < n; i++ {
		// Multiply g by (x + alpha^i).
		next := make([]byte, len(g)+1)
		root := gfExp[i]
		for j, c := range g {
			next[j] ^= c
			next[j+1] ^= gfMul(c, root)
		}
		g = next
	}
	return g
}

// rsEncode returns the ecLen error-correction codewords for data: the
// remainder of data*x^ecLen divided by the generator polynomial.
func rsEncode(data []byte, ecLen int) []byte {
	gen := rsGenerator(ecLen)
	rem := make([]byte, len(data)+ecLen)
	copy(rem, data)
	for i := 0; i < len(data); i++ {
		lead := rem[i]
		if lead == 0 {
			continue
		}
		for j, c := range gen {
			rem[i+j] ^= gfMul(c, lead)
		}
	}
	return rem[len(data):]
}

// ---------------------------------------------------------------------------
// BCH codes for the format and version information
// ---------------------------------------------------------------------------

// qrFormatBits returns the 15-bit format information for an error-correction
// level and mask pattern: a BCH(15,5) code word, XOR-masked with 0x5412 so
// that an all-zero format never occurs.
func qrFormatBits(level ecLevel, mask int) int {
	data := level.formatCode()<<3 | mask
	rem := data << 10
	for i := 14; i >= 10; i-- {
		if rem&(1<<i) != 0 {
			rem ^= 0x537 << (i - 10)
		}
	}
	return ((data << 10) | rem) ^ 0x5412
}

// qrVersionBits returns the 18-bit version information, a BCH(18,6) code word.
// Only versions 7 and above carry it.
func qrVersionBits(version int) int {
	rem := version << 12
	for i := 17; i >= 12; i-- {
		if rem&(1<<i) != 0 {
			rem ^= 0x1F25 << (i - 12)
		}
	}
	return version<<12 | rem&0xFFF
}

// ---------------------------------------------------------------------------
// The symbol
// ---------------------------------------------------------------------------

// qrCode is a finished QR symbol: a square grid of dark/light modules with no
// quiet zone. Rendering adds the quiet zone.
type qrCode struct {
	Size     int
	Version  int
	Level    ecLevel
	Mask     int
	modules  []bool // dark == true, row-major
	reserved []bool // true where a function pattern lives; never masked
}

func newQRCode(version int, level ecLevel) *qrCode {
	size := version*4 + 17
	return &qrCode{
		Size:     size,
		Version:  version,
		Level:    level,
		Mask:     -1,
		modules:  make([]bool, size*size),
		reserved: make([]bool, size*size),
	}
}

func (q *qrCode) inBounds(r, c int) bool { return r >= 0 && r < q.Size && c >= 0 && c < q.Size }

// At reports whether the module at row r, column c is dark. Out-of-range
// coordinates read as light, which is what the quiet zone is.
func (q *qrCode) At(r, c int) bool {
	if !q.inBounds(r, c) {
		return false
	}
	return q.modules[r*q.Size+c]
}

func (q *qrCode) set(r, c int, dark bool) {
	if q.inBounds(r, c) {
		q.modules[r*q.Size+c] = dark
	}
}

func (q *qrCode) reserve(r, c int) {
	if q.inBounds(r, c) {
		q.reserved[r*q.Size+c] = true
	}
}

func (q *qrCode) isReserved(r, c int) bool {
	if !q.inBounds(r, c) {
		return true
	}
	return q.reserved[r*q.Size+c]
}

// ---------------------------------------------------------------------------
// Encoding
// ---------------------------------------------------------------------------

var errQRTooLong = errors.New("payload does not fit in a version 10 QR code")

// byteModeCharCountBits is 8 for versions 1..9 and 16 for 10..26.
func byteModeCharCountBits(version int) int {
	if version <= 9 {
		return 8
	}
	return 16
}

// encodeQR builds a QR symbol for data in byte mode at the given
// error-correction level, choosing the smallest version that fits.
func encodeQR(data []byte, level ecLevel) (*qrCode, error) {
	return encodeQRMask(data, level, -1)
}

// encodeQRMask is encodeQR with an optional forced mask pattern; -1 means
// choose one by penalty score, which is what everything but the tests does.
func encodeQRMask(data []byte, level ecLevel, forceMask int) (*qrCode, error) {
	version := 0
	for v := 1; v <= maxQRVersion; v++ {
		spec := ecBlocks[v][level]
		need := 4 + byteModeCharCountBits(v) + 8*len(data)
		if need <= spec.dataCodewords()*8 {
			version = v
			break
		}
	}
	if version == 0 {
		return nil, fmt.Errorf("%w: %d bytes at level %s", errQRTooLong, len(data), level)
	}
	spec := ecBlocks[version][level]

	// 1. Bit stream: mode indicator, character count, the bytes themselves,
	//    terminator, byte alignment, then alternating pad codewords.
	bits := newBitBuffer()
	bits.append(0b0100, 4)
	bits.append(len(data), byteModeCharCountBits(version))
	for _, b := range data {
		bits.append(int(b), 8)
	}
	capacityBits := spec.dataCodewords() * 8
	for i := 0; i < 4 && bits.len() < capacityBits; i++ {
		bits.append(0, 1)
	}
	for bits.len()%8 != 0 {
		bits.append(0, 1)
	}
	pad := []int{0xEC, 0x11}
	for i := 0; bits.len() < capacityBits; i++ {
		bits.append(pad[i%2], 8)
	}
	dataCodewords := bits.bytes()

	// 2. Split into blocks, compute error correction for each.
	blocks := make([][]byte, 0, spec.blocks())
	ecs := make([][]byte, 0, spec.blocks())
	off := 0
	for i := 0; i < spec.blocks(); i++ {
		n := spec.dataG1
		if i >= spec.numG1 {
			n = spec.dataG2
		}
		block := dataCodewords[off : off+n]
		off += n
		blocks = append(blocks, block)
		ecs = append(ecs, rsEncode(block, spec.ecPerBlock))
	}

	// 3. Interleave: one codeword from each block in turn, data first, then
	//    error correction. Short blocks simply run out early.
	final := make([]byte, 0, totalCodewords[version])
	maxData := spec.dataG1
	if spec.dataG2 > maxData {
		maxData = spec.dataG2
	}
	for i := 0; i < maxData; i++ {
		for _, b := range blocks {
			if i < len(b) {
				final = append(final, b[i])
			}
		}
	}
	for i := 0; i < spec.ecPerBlock; i++ {
		for _, e := range ecs {
			final = append(final, e[i])
		}
	}

	// 4. Draw the function patterns, place the data, choose a mask.
	q := newQRCode(version, level)
	q.drawFunctionPatterns()
	q.placeCodewords(final)
	q.applyBestMask(forceMask)
	return q, nil
}

// bitBuffer accumulates a most-significant-bit-first bit stream.
type bitBuffer struct {
	buf  []byte
	nbit int
}

func newBitBuffer() *bitBuffer { return &bitBuffer{} }

func (b *bitBuffer) len() int { return b.nbit }

func (b *bitBuffer) append(value, width int) {
	for i := width - 1; i >= 0; i-- {
		if b.nbit%8 == 0 {
			b.buf = append(b.buf, 0)
		}
		if value&(1<<i) != 0 {
			b.buf[b.nbit/8] |= 1 << (7 - b.nbit%8)
		}
		b.nbit++
	}
}

func (b *bitBuffer) bytes() []byte { return b.buf }

// ---------------------------------------------------------------------------
// Function patterns
// ---------------------------------------------------------------------------

func (q *qrCode) drawFunctionPatterns() {
	size := q.Size

	// Three finder patterns with their one-module separators.
	q.drawFinder(0, 0)
	q.drawFinder(0, size-7)
	q.drawFinder(size-7, 0)

	// Timing patterns run between the finders on row 6 and column 6.
	for i := 8; i < size-8; i++ {
		dark := i%2 == 0
		q.set(6, i, dark)
		q.reserve(6, i)
		q.set(i, 6, dark)
		q.reserve(i, 6)
	}

	// Alignment patterns, skipping the three that collide with a finder.
	centers := alignmentCenters[q.Version]
	for _, r := range centers {
		for _, c := range centers {
			if (r == 6 && c == 6) || (r == 6 && c == size-7) || (r == size-7 && c == 6) {
				continue
			}
			q.drawAlignment(r, c)
		}
	}

	// Reserve the format information, and set the one module that is always
	// dark at (4*version+9, 8).
	for _, p := range q.formatPositions() {
		q.reserve(p[0], p[1])
	}
	q.set(size-8, 8, true)
	q.reserve(size-8, 8)

	// Reserve the version information blocks on versions 7 and up.
	if q.Version >= 7 {
		for i := 0; i < 18; i++ {
			q.reserve(size-11+i%3, i/3)
			q.reserve(i/3, size-11+i%3)
		}
	}
}

// drawFinder draws the 7x7 finder whose top-left corner is (top, left),
// together with the light separator that surrounds it.
func (q *qrCode) drawFinder(top, left int) {
	for dr := -1; dr <= 7; dr++ {
		for dc := -1; dc <= 7; dc++ {
			r, c := top+dr, left+dc
			if !q.inBounds(r, c) {
				continue
			}
			inSquare := dr >= 0 && dr <= 6 && dc >= 0 && dc <= 6
			dark := inSquare && (dr == 0 || dr == 6 || dc == 0 || dc == 6 ||
				(dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4))
			q.set(r, c, dark)
			q.reserve(r, c)
		}
	}
}

// drawAlignment draws the 5x5 alignment pattern centred on (row, col).
func (q *qrCode) drawAlignment(row, col int) {
	for dr := -2; dr <= 2; dr++ {
		for dc := -2; dc <= 2; dc++ {
			dark := dr == -2 || dr == 2 || dc == -2 || dc == 2 || (dr == 0 && dc == 0)
			q.set(row+dr, col+dc, dark)
			q.reserve(row+dr, col+dc)
		}
	}
}

// formatPositions lists the 30 modules that carry format information, in the
// order the 15 bits are written: index i and i+15 both hold bit i, least
// significant first.
func (q *qrCode) formatPositions() [][2]int {
	size := q.Size
	pos := make([][2]int, 0, 30)
	// First copy, wrapped around the top-left finder.
	for i := 0; i <= 5; i++ {
		pos = append(pos, [2]int{8, i})
	}
	pos = append(pos, [2]int{8, 7}, [2]int{8, 8}, [2]int{7, 8})
	for j := 0; j <= 5; j++ {
		pos = append(pos, [2]int{5 - j, 8})
	}
	// Second copy, split between the other two finders.
	for i := 0; i <= 6; i++ {
		pos = append(pos, [2]int{size - 1 - i, 8})
	}
	for j := 0; j <= 7; j++ {
		pos = append(pos, [2]int{8, size - 8 + j})
	}
	return pos
}

// ---------------------------------------------------------------------------
// Data placement, masking, format information
// ---------------------------------------------------------------------------

// placeCodewords walks the symbol in the standard two-module-wide zigzag,
// bottom-right to top-left, skipping the vertical timing column, and writes
// one bit into every module that is not part of a function pattern.
func (q *qrCode) placeCodewords(codewords []byte) {
	bit := 0
	total := len(codewords) * 8
	upward := true
	for col := q.Size - 1; col > 0; col -= 2 {
		if col == 6 {
			// Column 6 is the vertical timing pattern; the pair shifts left.
			col--
		}
		for i := 0; i < q.Size; i++ {
			row := i
			if upward {
				row = q.Size - 1 - i
			}
			for c := 0; c < 2; c++ {
				cc := col - c
				if q.isReserved(row, cc) {
					continue
				}
				dark := false
				if bit < total {
					dark = codewords[bit/8]&(1<<(7-bit%8)) != 0
				}
				q.set(row, cc, dark)
				bit++
			}
		}
		upward = !upward
	}
}

// maskCondition reports whether the mask flips the module at (row, col).
func maskCondition(mask, row, col int) bool {
	switch mask {
	case 0:
		return (row+col)%2 == 0
	case 1:
		return row%2 == 0
	case 2:
		return col%3 == 0
	case 3:
		return (row+col)%3 == 0
	case 4:
		return (row/2+col/3)%2 == 0
	case 5:
		return (row*col)%2+(row*col)%3 == 0
	case 6:
		return ((row*col)%2+(row*col)%3)%2 == 0
	case 7:
		return ((row+col)%2+(row*col)%3)%2 == 0
	}
	return false
}

// applyBestMask tries all eight mask patterns, keeps the one with the lowest
// penalty score, and writes the matching format information. A forced mask in
// [0,8) skips the search, which is what the tests use to compare placement
// against a reference independently of the scoring.
func (q *qrCode) applyBestMask(force int) {
	best, bestScore := 0, -1
	if force >= 0 && force < 8 {
		best = force
	} else {
		for mask := 0; mask < 8; mask++ {
			q.applyMask(mask)
			q.drawFormatInfo(mask)
			score := q.penalty()
			q.applyMask(mask) // masking is an XOR, so this undoes it
			if bestScore < 0 || score < bestScore {
				best, bestScore = mask, score
			}
		}
	}
	q.applyMask(best)
	q.drawFormatInfo(best)
	if q.Version >= 7 {
		q.drawVersionInfo()
	}
	q.Mask = best
}

func (q *qrCode) applyMask(mask int) {
	for r := 0; r < q.Size; r++ {
		for c := 0; c < q.Size; c++ {
			if q.isReserved(r, c) {
				continue
			}
			if maskCondition(mask, r, c) {
				q.modules[r*q.Size+c] = !q.modules[r*q.Size+c]
			}
		}
	}
}

// drawFormatInfo writes the 15 format bits into both of their copies.
//
// The two informations are stored in opposite bit orders, which is the single
// most common way to get a symbol that looks perfect and scans as nothing:
// format information is written MOST significant bit first along the position
// list, while version information (below) is written LEAST significant bit
// first. Both orders are pinned by tests against the standard's own tables.
func (q *qrCode) drawFormatInfo(mask int) {
	bits := qrFormatBits(q.Level, mask)
	pos := q.formatPositions()
	for i := 0; i < 15; i++ {
		dark := bits&(1<<(14-i)) != 0
		q.set(pos[i][0], pos[i][1], dark)
		q.set(pos[i+15][0], pos[i+15][1], dark)
	}
}

func (q *qrCode) drawVersionInfo() {
	bits := qrVersionBits(q.Version)
	for i := 0; i < 18; i++ {
		dark := bits&(1<<i) != 0
		q.set(q.Size-11+i%3, i/3, dark)
		q.set(i/3, q.Size-11+i%3, dark)
	}
}

// ---------------------------------------------------------------------------
// Penalty scoring (ISO/IEC 18004 section 8.8.2)
// ---------------------------------------------------------------------------

func (q *qrCode) penalty() int {
	return q.penaltyRuns() + q.penaltyBlocks() + q.penaltyFinderLike() + q.penaltyBalance()
}

// penaltyRuns charges 3 for each run of five same-coloured modules in a row or
// column, plus 1 for every module beyond the fifth.
func (q *qrCode) penaltyRuns() int {
	score := 0
	count := func(get func(i int) bool) {
		run, prev := 1, get(0)
		for i := 1; i < q.Size; i++ {
			cur := get(i)
			if cur == prev {
				run++
				continue
			}
			if run >= 5 {
				score += 3 + (run - 5)
			}
			run, prev = 1, cur
		}
		if run >= 5 {
			score += 3 + (run - 5)
		}
	}
	for r := 0; r < q.Size; r++ {
		count(func(i int) bool { return q.At(r, i) })
	}
	for c := 0; c < q.Size; c++ {
		count(func(i int) bool { return q.At(i, c) })
	}
	return score
}

// penaltyBlocks charges 3 for every 2x2 block of one colour.
func (q *qrCode) penaltyBlocks() int {
	score := 0
	for r := 0; r < q.Size-1; r++ {
		for c := 0; c < q.Size-1; c++ {
			v := q.At(r, c)
			if q.At(r, c+1) == v && q.At(r+1, c) == v && q.At(r+1, c+1) == v {
				score += 3
			}
		}
	}
	return score
}

// finderLike is the 1:1:3:1:1 sequence that a scanner mistakes for a finder,
// scored 40 each time it appears in a row or column with four light modules on
// either side.
var finderLike = []bool{true, false, true, true, true, false, true, false, false, false, false}

func matchesAt(get func(i int) bool, at int, pattern []bool) bool {
	for i, want := range pattern {
		if get(at+i) != want {
			return false
		}
	}
	return true
}

func (q *qrCode) penaltyFinderLike() int {
	score := 0
	reversed := make([]bool, len(finderLike))
	for i, v := range finderLike {
		reversed[len(finderLike)-1-i] = v
	}
	scan := func(get func(i int) bool) {
		for at := 0; at+len(finderLike) <= q.Size; at++ {
			if matchesAt(get, at, finderLike) {
				score += 40
			}
			if matchesAt(get, at, reversed) {
				score += 40
			}
		}
	}
	for r := 0; r < q.Size; r++ {
		scan(func(i int) bool { return q.At(r, i) })
	}
	for c := 0; c < q.Size; c++ {
		scan(func(i int) bool { return q.At(i, c) })
	}
	return score
}

// penaltyBalance charges 10 for every 5% the proportion of dark modules
// deviates from half.
func (q *qrCode) penaltyBalance() int {
	dark := 0
	for _, m := range q.modules {
		if m {
			dark++
		}
	}
	total := q.Size * q.Size
	percent := dark * 100 / total
	// Distance from 50%, rounded outwards to a multiple of 5.
	low := percent / 5 * 5
	high := low + 5
	dLow := abs(low - 50)
	dHigh := abs(high - 50)
	if dLow < dHigh {
		return dLow / 5 * 10
	}
	return dHigh / 5 * 10
}

func abs(n int) int {
	if n < 0 {
		return -n
	}
	return n
}

// ---------------------------------------------------------------------------
// Terminal rendering
// ---------------------------------------------------------------------------

const qrQuietZone = 4

// renderQRBlocks draws the symbol with Unicode half-blocks, two module rows per
// text line. Light modules are drawn as block characters so they appear in the
// terminal's foreground colour and dark modules are left as the background:
// on the usual light-on-dark terminal that is the right way round for a
// scanner. Use renderQRInverted on a light background.
func renderQRBlocks(q *qrCode, inverted bool) string {
	var sb strings.Builder
	lo, hi := -qrQuietZone, q.Size+qrQuietZone
	for r := lo; r < hi; r += 2 {
		for c := lo; c < hi; c++ {
			top, bottom := q.At(r, c), q.At(r+1, c)
			if r+1 >= hi {
				bottom = false
			}
			if inverted {
				top, bottom = !top, !bottom
			}
			// A "light" module is drawn; a dark one is left blank.
			switch {
			case !top && !bottom:
				sb.WriteRune('█') // full block
			case !top && bottom:
				sb.WriteRune('▀') // upper half
			case top && !bottom:
				sb.WriteRune('▄') // lower half
			default:
				sb.WriteRune(' ')
			}
		}
		sb.WriteByte('\n')
	}
	return sb.String()
}

// renderQRASCII draws the symbol with two ASCII characters per module, for
// terminals and log files that cannot show block characters. Dark modules are
// "##", light modules two spaces.
func renderQRASCII(q *qrCode) string {
	var sb strings.Builder
	lo, hi := -qrQuietZone, q.Size+qrQuietZone
	for r := lo; r < hi; r++ {
		for c := lo; c < hi; c++ {
			if q.At(r, c) {
				sb.WriteString("##")
			} else {
				sb.WriteString("  ")
			}
		}
		sb.WriteByte('\n')
	}
	return sb.String()
}

// renderQR picks a renderer by name. Unknown names fall back to blocks.
func renderQR(q *qrCode, style string) string {
	switch strings.ToLower(strings.TrimSpace(style)) {
	case "ascii":
		return renderQRASCII(q)
	case "invert", "inverted", "light":
		return renderQRBlocks(q, true)
	default:
		return renderQRBlocks(q, false)
	}
}
