package standard

import (
	"fmt"
	"image"
	"image/color"

	"github.com/yeqown/go-qrcode/v2"
)

type ImageOption interface {
	apply(o *outputImageOptions)
}

// defaultOutputImageOption default output image background color and etc options
func defaultOutputImageOption() *outputImageOptions {
	return &outputImageOptions{
		bgColor:            color_WHITE, // white
		bgTransparent:      false,       // not transparent
		qrColor:            color_BLACK, // black
		logo:               nil,         //
		logoSizeMultiplier: 5,
		qrWidth:            20,              //
		shape:              _shapeRectangle, //
		imageEncoder:       jpegEncoder{},
		borderWidths:       [4]int{_defaultPadding, _defaultPadding, _defaultPadding, _defaultPadding},
	}
}

// outputImageOptions to output QR code image
type outputImageOptions struct {
	// bgColor is the background color of the QR code image.
	bgColor color.RGBA
	// bgTransparent only affects on PNG_FORMAT
	bgTransparent bool

	// qrColor is the foreground color of the QR code.
	qrColor color.RGBA

	// qrGradient is an optional linear gradient to apply to QR modules instead of a solid color.
	qrGradient *LinearGradient

	// logo this icon image would be put the center of QR Code image
	// NOTE: logo only should have 1 / logoSizeMultiplier size of QRCode image
	logo image.Image

	logoSizeMultiplier int

	// logoSafeZone indicates whether to reserve a clear area around the logo,
	// preventing QR code blocks from being drawn underneath it.
	logoSafeZone bool

	// qrWidth width of each qr block
	qrWidth int

	// shape means how to draw the shape of each cell.
	shape IShape

	// imageEncoder specify which file format would be encoded the QR image.
	imageEncoder ImageEncoder

	// borderWidths indicates the border width of the output image. the order is
	// top, right, bottom, left same as the WithBorder
	borderWidths [4]int

	// halftoneImg is the halftone image for the output image.
	halftoneImg image.Image
}

func (oo *outputImageOptions) backgroundColor() color.RGBA {
	if oo == nil {
		return color_WHITE
	}

	if oo.bgTransparent {
		(&oo.bgColor).A = 0x00
	}

	return oo.bgColor
}

func (oo *outputImageOptions) logoImage() image.Image {
	if oo == nil || oo.logo == nil {
		return nil
	}

	return oo.logo
}

func (oo *outputImageOptions) qrBlockWidth() int {
	if oo == nil || (oo.qrWidth <= 0 || oo.qrWidth > 255) {
		return 20
	}

	return oo.qrWidth
}

func (oo *outputImageOptions) getShape() IShape {
	if oo == nil || oo.shape == nil {
		return _shapeRectangle
	}

	return oo.shape
}

// preCalculateAttribute this function must reference to draw function.
func (oo *outputImageOptions) preCalculateAttribute(dimension int) *Attribute {
	if oo == nil {
		return nil
	}

	top, right, bottom, left := oo.borderWidths[0], oo.borderWidths[1], oo.borderWidths[2], oo.borderWidths[3]
	return &Attribute{
		W:          dimension*oo.qrBlockWidth() + right + left,
		H:          dimension*oo.qrBlockWidth() + top + bottom,
		Borders:    oo.borderWidths,
		BlockWidth: oo.qrBlockWidth(),
	}
}

var (
	color_WHITE = parseFromHex("#ffffff")
	color_BLACK = parseFromHex("#000000")
)

var (
	// _STATE_MAPPING mapping matrix.State to color.RGBA in debug mode.
	_STATE_MAPPING = map[qrcode.QRType]color.RGBA{
		qrcode.QRType_INIT:     parseFromHex("#ffffff"), // [bg]
		qrcode.QRType_DATA:     parseFromHex("#cdc9c3"), // [bg]
		qrcode.QRType_VERSION:  parseFromHex("#000000"), // [fg]
		qrcode.QRType_FORMAT:   parseFromHex("#444444"), // [fg]
		qrcode.QRType_FINDER:   parseFromHex("#555555"), // [fg]
		qrcode.QRType_DARK:     parseFromHex("#2BA859"), // [fg]
		qrcode.QRType_SPLITTER: parseFromHex("#2BA859"), // [fg]
		qrcode.QRType_TIMING:   parseFromHex("#000000"), // [fg]
	}
)

// translateToRGBA get color.RGBA by value State, if not found, return outputImageOptions.qrColor.
// NOTE: this function decides the state should use qrColor or bgColor.
func (oo *outputImageOptions) translateToRGBA(v qrcode.QRValue) (rgba color.RGBA) {
	// TODO(@yeqown): use _STATE_MAPPING to replace this function while in debug mode
	// or some special flag.
	if v.IsSet() {
		rgba = oo.qrColor
		return rgba
	}

	if oo.bgTransparent {
		(&oo.bgColor).A = 0x00
		// color.RGBA is pre-multiplied by alpha, so set RGB to 0 when fully transparent.
		(&oo.bgColor).R = 0x00
		(&oo.bgColor).G = 0x00
		(&oo.bgColor).B = 0x00
	}
	rgba = oo.bgColor

	return rgba
}

// parseFromHex convert hex string into color.RGBA
func parseFromHex(s string) color.RGBA {
	c := color.RGBA{
		R: 0,
		G: 0,
		B: 0,
		A: 0xff,
	}

	var err error
	switch len(s) {
	case 7:
		_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
	case 4:
		_, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B)
		// Double the hex digits:
		c.R *= 17
		c.G *= 17
		c.B *= 17
	default:
		err = fmt.Errorf("invalid length, must be 7 or 4")
	}
	if err != nil {
		panic(err)
	}

	return c
}

func parseFromColor(c color.Color) color.RGBA {
	rgba, ok := c.(color.RGBA)
	if ok {
		return rgba
	}

	r, g, b, a := c.RGBA()
	return color.RGBA{
		R: uint8(r),
		G: uint8(g),
		B: uint8(b),
		A: uint8(a),
	}
}
