package d2isometricimg

import (
	
	
	
	
	
	
	
	
	

	
)

// A single palette is sampled across four traffic phases. Quantization then
// uses an immutable RGB lookup, without error diffusion: changing one particle
// cannot change the indexed color of otherwise identical neighboring pixels.
func renderGIF( *capture) ([]byte, error) {
	,  := nativeGIFCycle(.scene)
	if  != FrameCount {
		,  := nativeGIFLoopScene(.ctx, .scene, time.Duration(*float64(time.Second)))
		if  != nil {
			return nil, 
		}
		 := *
		.scene = 
		 = &
	}
	 := func( float64) (*image.RGBA, error) {
		return .frameImageAt(, *CycleSeconds/, true)
	}
	 := image.NewRGBA(image.Rect(0, 0, 256, 1024))
	for  := 0;  < 4; ++ {
		 := float64() *  / 4
		if  != FrameCount {
			// Whole-second boundaries would sample the same authored pulse.
			 += float64() / 4
		}
		,  := ()
		if  != nil {
			return nil, 
		}
		for  := 0;  < 256; ++ {
			for  := 0;  < 256; ++ {
				.Set(, *256+, .At(*.opts.Width/256, *.opts.Height/256))
			}
		}
	}
	if  := .ctx.Err();  != nil {
		return nil, 
	}
	 := quantize.MedianCutQuantizer{}.Quantize(make(color.Palette, 0, 256), )
	,  := paletteLookup(.ctx, )
	if  != nil {
		return nil, 
	}
	 := &gif.GIF{LoopCount: 0, Config: image.Config{ColorModel: , Width: .opts.Width, Height: .opts.Height}}
	var  *image.Paletted
	for  := 0;  < ; ++ {
		// Do not repeat phase 1: the next loop's phase 0 is its next sample.
		,  := (float64() *  / float64())
		if  != nil {
			return nil, 
		}
		,  := indexFrame(.ctx, , , )
		if  != nil {
			return nil, 
		}
		// Delta rectangles retain the previous opaque canvas, saving bytes and
		// memory while still erasing each particle's old position correctly.
		 := 
		if  != nil {
			 = frameDelta(, )
		}
		.Image = append(.Image, )
		.Delay = append(.Delay, cycleFrameDelay(, , int(math.Round(*100))))
		.Disposal = append(.Disposal, gif.DisposalNone)
		 = 
	}
	var  bytes.Buffer
	 := &boundedWriter{ctx: .ctx, w: &, remaining: MaxOutputBytes}
	if  := gif.EncodeAll(, );  != nil {
		return nil, 
	}
	if  := .ctx.Err();  != nil {
		return nil, 
	}
	return .Bytes(), nil
}

func nativeGIFCycle( *nativeScene) ( int,  float64) {
	if len(.animatedNodes) > 0 {
		return 96, 8
	}
	for ,  := range .panels {
		if .animated {
			return 96, 8
		}
	}
	return FrameCount, CycleSeconds
}

func frameDelay( int) int {
	// GIF measures time in centiseconds. Round cumulative boundaries so the
	// complete 8.333...s cycle is represented by 833cs, not 800 or 900cs.
	return cycleFrameDelay(, FrameCount, 833)
}

func cycleFrameDelay(, ,  int) int {
	return int(math.Round(float64(+1)*float64()/float64()) - math.Round(float64()*float64()/float64()))
}

func paletteLookup( context.Context,  color.Palette) ([]uint8, error) {
	 := make([]uint8, 1<<18)
	for  := 0;  < 64; ++ {
		if  := .Err();  != nil {
			return nil, 
		}
		for  := 0;  < 64; ++ {
			for  := 0;  < 64; ++ {
				[<<12|<<6|] = uint8(.Index(color.RGBA{uint8( * 255 / 63), uint8( * 255 / 63), uint8( * 255 / 63), 255}))
			}
		}
	}
	return , nil
}

func indexFrame( context.Context,  image.Image,  color.Palette,  []uint8) (*image.Paletted, error) {
	 := .Bounds()
	 := image.NewPaletted(, )
	for  := .Min.Y;  < .Max.Y; ++ {
		if %32 == 0 {
			if  := .Err();  != nil {
				return nil, 
			}
		}
		for  := .Min.X;  < .Max.X; ++ {
			, , ,  := .At(, ).RGBA()
			.SetColorIndex(, , [int(>>10)<<12|int(>>10)<<6|int(>>10)])
		}
	}
	return , nil
}

func frameDelta(,  *image.Paletted) *image.Paletted {
	 := .Bounds()
	, , ,  := .Max.X, .Max.Y, .Min.X, .Min.Y
	for  := .Min.Y;  < .Max.Y; ++ {
		for  := .Min.X;  < .Max.X; ++ {
			if .ColorIndexAt(, ) != .ColorIndexAt(, ) {
				 = min(, )
				 = min(, )
				 = max(, +1)
				 = max(, +1)
			}
		}
	}
	if  >=  {
		, , ,  = 0, 0, 1, 1
	}
	 := image.Rect(, , , )
	 := image.NewPaletted(, .Palette)
	for  := ;  < ; ++ {
		copy(.Pix[(-)*.Stride:], .Pix[.PixOffset(, ):.PixOffset(, )])
	}
	return 
}

type boundedWriter struct {
	ctx       context.Context
	w         io.Writer
	remaining int64
}

func ( *boundedWriter) ( []byte) (int, error) {
	if  := .ctx.Err();  != nil {
		return 0, 
	}
	if int64(len()) > .remaining {
		return 0, fmt.Errorf("isometric image exceeds encoded byte limit")
	}
	,  := .w.Write()
	.remaining -= int64()
	if  == nil &&  != len() {
		 = io.ErrShortWrite
	}
	return , 
}