package nori

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"image"
	"image/color"
	"io"
)

type Image struct {
	Bpp       uint32
	Count     uint32
	SubImages []*SubImage
}

type SubImage struct {
	Delay   uint32
	OffsetX int32
	OffsetY int32

	Width  uint32
	Height uint32

	Size uint32

	Img image.Image
	rgb *image.RGBA64
}

func (i *SubImage) decode(rd io.Reader, img *Image, size int, g *GawiSection) error {
	switch img.Bpp {
	case 8:
		if g.Palette == nil {
			return fmt.Errorf("bpp = 8, but no palette")
		}
		for idx := 0; idx < int(size); idx++ {
			b, err := readByte(rd)
			if err != nil {
				return err
			}
			col := g.Palette.Palette[b]
			i.rgb.Set(idx%int(i.Width), idx/int(i.Width), col)
		}
	case 16:
		var bgr uint16
		for idx := 0; idx < int(size/2); idx++ {
			if err := binary.Read(rd, end, &bgr); err != nil {
				return err
			}
			red := uint8(((0x7c00 & bgr) >> 10) * 255 / 31)
			green := uint8(((0x3e0 & bgr) >> 5) * 255 / 31)
			blue := uint8((0x1f & bgr) * 255 / 31)
			alpha := uint8(0xff)
			if red == 0xff && green == 0x00 && blue == 0xff {
				alpha = 0x00
			}
			if red == 0x00 && blue == 0x00 && green == 0xff {
				alpha = 0x00
			}
			col := &color.RGBA{
				red, green, blue,
				alpha,
			}
			i.rgb.Set(idx%int(i.Width), idx/int(i.Width), col)
		}
	case 24:
		for idx := 0; idx < int(size/3); idx++ {
			rgb := [3]byte{}
			_, err := io.ReadFull(rd, rgb[:])
			if err != nil {
				return err
			}
			alpha := uint8(0xff)

			if rgb[2] == 0x00 && rgb[1] == 0xff && rgb[0] == 0x00 {
				alpha = 0x00
			}
			if rgb[2] == 0xff && rgb[1] == 0x00 && rgb[0] == 0xff {
				alpha = 0x00
			}

			col := &color.RGBA{
				rgb[2], rgb[1], rgb[0],
				alpha,
			}
			i.rgb.Set(idx%int(i.Width), idx/int(i.Width), col)
		}
	default:
		return fmt.Errorf("unsupported bpp, %d", img.Bpp)
	}

	return nil
}

func (i *SubImage) decodeCompressed(rd io.Reader, img *Image, g *GawiSection) error {
	var u16 uint16
	ob := new(bytes.Buffer)
	for k := 0; k < int(i.Height); k++ {
		pixelCount := 0
		if err := binary.Read(rd, end, &u16); err != nil {
			return fmt.Errorf("compressed size: %w", err)
		}
		want := make([]byte, int(u16)-2)
		if _, err := io.ReadFull(rd, want); err != nil {
			return fmt.Errorf("compressed want (%d): %w", u16, err)
		}
		buf := bytes.NewBuffer(want)
		for buf.Len() > 0 {
			var paddingSize, foregroundSize uint16
			if err := binary.Read(buf, end, &paddingSize); err != nil {
				return fmt.Errorf("padding size: %w", err)
			}
			if err := binary.Read(buf, end, &foregroundSize); err != nil {
				return fmt.Errorf("foreground size: %w", err)
			}
			for x := 0; x < int(paddingSize); x++ {
				switch img.Bpp {
				case 8:
					ob.Write([]byte{0})
				case 16:
					ob.Write([]byte{0x1f, 0x7c})
				case 24:
					ob.Write([]byte{0xff, 0, 0xff})
				default:
					ob.Write([]byte{0})
				}
			}
			foregroundBytes := make([]byte, int(foregroundSize)*(int(g.Bpp)/8))
			if _, err := io.ReadFull(buf, foregroundBytes); err != nil {
				return fmt.Errorf("foreground bytes: %w", err)
			}
			for y := 0; y < len(foregroundBytes); y++ {
				ob.WriteByte(foregroundBytes[y])
			}
			pixelCount = pixelCount + int(paddingSize) + int(foregroundSize)
		}
		if pixelCount != int(i.Width) {
			return fmt.Errorf("scanline has fewer pixels than the width (expected %d, got %d)", i.Width, pixelCount)
		}
	}
	return i.decode(ob, img, ob.Len(), g)
}

func (i *SubImage) Decode(rd io.Reader, img *Image, g *GawiSection) error {
	cf := image.NewRGBA64(image.Rect(0, 0, int(i.Width), int(i.Height)))
	i.rgb = cf
	i.Img = i.rgb
	if g.IsCompressed {
		return i.decodeCompressed(rd, img, g)
	}
	return i.decode(rd, img, int(i.Size), g)
}

func (img *Image) Decode(rd io.Reader, g *GawiSection) error {
	if err := binary.Read(rd, end, &img.Count); err != nil {
		return err
	}
	img.SubImages = make([]*SubImage, 0, img.Count)
	for k := 0; k < int(img.Count); k++ {
		i := &SubImage{}
		img.SubImages = append(img.SubImages, i)
		if err := binary.Read(rd, end, &i.Size); err != nil {
			return err
		}

		if err := binary.Read(rd, end, &i.Width); err != nil {
			return err
		}

		if err := binary.Read(rd, end, &i.Height); err != nil {
			return err
		}

		if err := binary.Read(rd, end, &i.Delay); err != nil {
			return err
		}

		if err := binary.Read(rd, end, &i.OffsetX); err != nil {
			return err
		}

		if err := binary.Read(rd, end, &i.OffsetY); err != nil {
			return err
		}
		if err := i.Decode(rd, img, g); err != nil {
			return err
		}
	}
	return nil
}
