package kitty

import (
	"encoding"
	"fmt"
	"strconv"
	"strings"
)

var (
	_ encoding.TextMarshaler   = Options{}
	_ encoding.TextUnmarshaler = &Options{}
)

// Options represents a Kitty Graphics Protocol options.
type Options struct {
	// Common options.

	// Action (a=t) is the action to be performed on the image. Can be one of
	// [Transmit], [TransmitDisplay], [Query], [Put], [Delete], [Frame],
	// [Animate], [Compose].
	Action byte

	// Quite mode (q=0) is the quiet mode. Can be either zero, one, or two
	// where zero is the default, 1 suppresses OK responses, and 2 suppresses
	// both OK and error responses.
	Quite byte

	// Transmission options.

	// ID (i=) is the image ID. The ID is a unique identifier for the image.
	// Must be a positive integer up to [math.MaxUint32].
	ID int

	// PlacementID (p=) is the placement ID. The placement ID is a unique
	// identifier for the placement of the image. Must be a positive integer up
	// to [math.MaxUint32].
	PlacementID int

	// Number (I=0) is the number of images to be transmitted.
	Number int

	// Format (f=32) is the image format. One of [RGBA], [RGB], [PNG].
	Format int

	// ImageWidth (s=0) is the transmitted image width.
	ImageWidth int

	// ImageHeight (v=0) is the transmitted image height.
	ImageHeight int

	// Compression (o=) is the image compression type. Can be [Zlib] or zero.
	Compression byte

	// Transmission (t=d) is the image transmission type. Can be [Direct], [File],
	// [TempFile], or[SharedMemory].
	Transmission byte

	// File is the file path to be used when the transmission type is [File].
	// If [Options.Transmission] is omitted i.e. zero and this is non-empty,
	// the transmission type is set to [File].
	File string

	// Size (S=0) is the size to be read from the transmission medium.
	Size int

	// Offset (O=0) is the offset byte to start reading from the transmission
	// medium.
	Offset int

	// Chunk (m=) whether the image is transmitted in chunks. Can be either
	// zero or one. When true, the image is transmitted in chunks. Each chunk
	// must be a multiple of 4, and up to [MaxChunkSize] bytes. Each chunk must
	// have the m=1 option except for the last chunk which must have m=0.
	Chunk bool

	// Display options.

	// X (x=0) is the pixel X coordinate of the image to start displaying.
	X int

	// Y (y=0) is the pixel Y coordinate of the image to start displaying.
	Y int

	// Z (z=0) is the Z coordinate of the image to display.
	Z int

	// Width (w=0) is the width of the image to display.
	Width int

	// Height (h=0) is the height of the image to display.
	Height int

	// OffsetX (X=0) is the OffsetX coordinate of the cursor cell to start
	// displaying the image. OffsetX=0 is the leftmost cell. This must be
	// smaller than the terminal cell width.
	OffsetX int

	// OffsetY (Y=0) is the OffsetY coordinate of the cursor cell to start
	// displaying the image. OffsetY=0 is the topmost cell. This must be
	// smaller than the terminal cell height.
	OffsetY int

	// Columns (c=0) is the number of columns to display the image. The image
	// will be scaled to fit the number of columns.
	Columns int

	// Rows (r=0) is the number of rows to display the image. The image will be
	// scaled to fit the number of rows.
	Rows int

	// VirtualPlacement (U=0) whether to use virtual placement. This is used
	// with Unicode [Placeholder] to display images.
	VirtualPlacement bool

	// DoNotMoveCursor (C=0) whether to move the cursor after displaying the
	// image.
	DoNotMoveCursor bool

	// ParentID (P=0) is the parent image ID. The parent ID is the ID of the
	// image that is the parent of the current image. This is used with Unicode
	// [Placeholder] to display images relative to the parent image.
	ParentID int

	// ParentPlacementID (Q=0) is the parent placement ID. The parent placement
	// ID is the ID of the placement of the parent image. This is used with
	// Unicode [Placeholder] to display images relative to the parent image.
	ParentPlacementID int

	// Delete options.

	// Delete (d=a) is the delete action. Can be one of [DeleteAll],
	// [DeleteID], [DeleteNumber], [DeleteCursor], [DeleteFrames],
	// [DeleteCell], [DeleteCellZ], [DeleteRange], [DeleteColumn], [DeleteRow],
	// [DeleteZ].
	Delete byte

	// DeleteResources indicates whether to delete the resources associated
	// with the image.
	DeleteResources bool
}

// Options returns the options as a slice of a key-value pairs.
func (o *Options) Options() (opts []string) {
	opts = []string{}
	if o.Format == 0 {
		o.Format = RGBA
	}

	if o.Action == 0 {
		o.Action = Transmit
	}

	if o.Delete == 0 {
		o.Delete = DeleteAll
	}

	if o.Transmission == 0 {
		if len(o.File) > 0 {
			o.Transmission = File
		} else {
			o.Transmission = Direct
		}
	}

	if o.Format != RGBA {
		opts = append(opts, fmt.Sprintf("f=%d", o.Format))
	}

	if o.Quite > 0 {
		opts = append(opts, fmt.Sprintf("q=%d", o.Quite))
	}

	if o.ID > 0 {
		opts = append(opts, fmt.Sprintf("i=%d", o.ID))
	}

	if o.PlacementID > 0 {
		opts = append(opts, fmt.Sprintf("p=%d", o.PlacementID))
	}

	if o.Number > 0 {
		opts = append(opts, fmt.Sprintf("I=%d", o.Number))
	}

	if o.ImageWidth > 0 {
		opts = append(opts, fmt.Sprintf("s=%d", o.ImageWidth))
	}

	if o.ImageHeight > 0 {
		opts = append(opts, fmt.Sprintf("v=%d", o.ImageHeight))
	}

	if o.Transmission != Direct {
		opts = append(opts, fmt.Sprintf("t=%c", o.Transmission))
	}

	if o.Size > 0 {
		opts = append(opts, fmt.Sprintf("S=%d", o.Size))
	}

	if o.Offset > 0 {
		opts = append(opts, fmt.Sprintf("O=%d", o.Offset))
	}

	if o.Compression == Zlib {
		opts = append(opts, fmt.Sprintf("o=%c", o.Compression))
	}

	if o.VirtualPlacement {
		opts = append(opts, "U=1")
	}

	if o.DoNotMoveCursor {
		opts = append(opts, "C=1")
	}

	if o.ParentID > 0 {
		opts = append(opts, fmt.Sprintf("P=%d", o.ParentID))
	}

	if o.ParentPlacementID > 0 {
		opts = append(opts, fmt.Sprintf("Q=%d", o.ParentPlacementID))
	}

	if o.X > 0 {
		opts = append(opts, fmt.Sprintf("x=%d", o.X))
	}

	if o.Y > 0 {
		opts = append(opts, fmt.Sprintf("y=%d", o.Y))
	}

	if o.Z > 0 {
		opts = append(opts, fmt.Sprintf("z=%d", o.Z))
	}

	if o.Width > 0 {
		opts = append(opts, fmt.Sprintf("w=%d", o.Width))
	}

	if o.Height > 0 {
		opts = append(opts, fmt.Sprintf("h=%d", o.Height))
	}

	if o.OffsetX > 0 {
		opts = append(opts, fmt.Sprintf("X=%d", o.OffsetX))
	}

	if o.OffsetY > 0 {
		opts = append(opts, fmt.Sprintf("Y=%d", o.OffsetY))
	}

	if o.Columns > 0 {
		opts = append(opts, fmt.Sprintf("c=%d", o.Columns))
	}

	if o.Rows > 0 {
		opts = append(opts, fmt.Sprintf("r=%d", o.Rows))
	}

	if o.Delete != DeleteAll || o.DeleteResources {
		da := o.Delete
		if o.DeleteResources {
			da = da - ' ' // to uppercase
		}

		opts = append(opts, fmt.Sprintf("d=%c", da))
	}

	if o.Action != Transmit {
		opts = append(opts, fmt.Sprintf("a=%c", o.Action))
	}

	return
}

// String returns the string representation of the options.
func (o Options) String() string {
	return strings.Join(o.Options(), ",")
}

// MarshalText returns the string representation of the options.
func (o Options) MarshalText() ([]byte, error) {
	return []byte(o.String()), nil
}

// UnmarshalText parses the options from the given string.
func (o *Options) UnmarshalText(text []byte) error {
	opts := strings.Split(string(text), ",")
	for _, opt := range opts {
		ps := strings.SplitN(opt, "=", 2)
		if len(ps) != 2 || len(ps[1]) == 0 {
			continue
		}

		switch ps[0] {
		case "a":
			o.Action = ps[1][0]
		case "o":
			o.Compression = ps[1][0]
		case "t":
			o.Transmission = ps[1][0]
		case "d":
			d := ps[1][0]
			if d >= 'A' && d <= 'Z' {
				o.DeleteResources = true
				d = d + ' ' // to lowercase
			}
			o.Delete = d
		case "i", "q", "p", "I", "f", "s", "v", "S", "O", "m", "x", "y", "z", "w", "h", "X", "Y", "c", "r", "U", "P", "Q":
			v, err := strconv.Atoi(ps[1])
			if err != nil {
				continue
			}

			switch ps[0] {
			case "i":
				o.ID = v
			case "q":
				o.Quite = byte(v)
			case "p":
				o.PlacementID = v
			case "I":
				o.Number = v
			case "f":
				o.Format = v
			case "s":
				o.ImageWidth = v
			case "v":
				o.ImageHeight = v
			case "S":
				o.Size = v
			case "O":
				o.Offset = v
			case "m":
				o.Chunk = v == 0 || v == 1
			case "x":
				o.X = v
			case "y":
				o.Y = v
			case "z":
				o.Z = v
			case "w":
				o.Width = v
			case "h":
				o.Height = v
			case "X":
				o.OffsetX = v
			case "Y":
				o.OffsetY = v
			case "c":
				o.Columns = v
			case "r":
				o.Rows = v
			case "U":
				o.VirtualPlacement = v == 1
			case "P":
				o.ParentID = v
			case "Q":
				o.ParentPlacementID = v
			}
		}
	}

	return nil
}
