package d2scenebuild

import (
	
	
	
	
	
	
	
	
	
	

	
	
	
)

const (
	paperPatternSourceSHA256    = "98b3c110441c6c02cb685c0a7303e7a36c1faee7fa1f65f6424b48ec51123527"
	paperPatternPathCount       = 1042
	paperPatternCommandCount    = 10926
	paperPatternMaskPathData    = "M75.0871 -0.0878906H-0.0878906V75.0871H75.0871V-0.0878906Z"
	grainPatternPNGSourceSHA256 = "7aec37844fa033cbdb586efaa1955dc6f43d56707c0b1bf91e8b5e32386d8d69"
	grainPatternPNGBytes        = 20845
	grainPatternPixelWidth      = 466
	grainPatternPixelHeight     = 349
	grainPatternDecodedBytes    = int64(grainPatternPixelWidth * grainPatternPixelHeight * 4)
	sketchStreakCommandCount    = 687
)

type paperPatternPath struct {
	color    uint8
	commands []d2scene.PathCommand
}

type paperPatternSource struct {
	paths []paperPatternPath
}

type grainPatternSource struct {
	png          []byte
	pixelWidth   int
	pixelHeight  int
	decodedBytes int64
}

type patternSourceCache[ any] struct {
	mu      sync.Mutex
	loading chan struct{}
	value   
	ready   bool
}

func ( *patternSourceCache[]) ( context.Context,  func(context.Context) (, error)) (, error) {
	var  
	if  == nil {
		 = context.Background()
	}
	for {
		if  := .Err();  != nil {
			return , 
		}
		.mu.Lock()
		if .ready {
			 := .value
			.mu.Unlock()
			return , nil
		}
		if  := .loading;  != nil {
			.mu.Unlock()
			select {
			case <-.Done():
				return , .Err()
			case <-:
				continue
			}
		}

		 := make(chan struct{})
		.loading = 
		.mu.Unlock()
		,  := ()
		.mu.Lock()
		if  == nil {
			.value = 
			.ready = true
		}
		.loading = nil
		close()
		.mu.Unlock()
		return , 
	}
}

var (
	paperPatternCache  patternSourceCache[*paperPatternSource]
	grainPatternCache  patternSourceCache[*grainPatternSource]
	streakPatternCache patternSourceCache[[]d2scene.PathCommand]
)

func sharedPaperPatternSource( context.Context) (*paperPatternSource, error) {
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	return paperPatternCache.load(, func( context.Context) (*paperPatternSource, error) {
		return parsePaperPatternSource(, patternassets.PaperSVG())
	})
}

func sharedGrainPatternSource( context.Context) (*grainPatternSource, error) {
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	return grainPatternCache.load(, func( context.Context) (*grainPatternSource, error) {
		return parseGrainPatternSource(, patternassets.GrainPNG())
	})
}

func sharedSketchStreakCommands( context.Context) ([]d2scene.PathCommand, error) {
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	return streakPatternCache.load(, func( context.Context) ([]d2scene.PathCommand, error) {
		return parseSketchStreakSource(, patternassets.StreakPathData())
	})
}

func parsePaperPatternSource( context.Context,  string) (*paperPatternSource, error) {
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	if sourceStringDigest() != paperPatternSourceSHA256 {
		return nil, fmt.Errorf("paper definition digest does not match its asset ID")
	}
	type  struct {
		  string
		 uint8
	}
	 := make([], 0, paperPatternPathCount+1)
	 := xml.NewDecoder(strings.NewReader())
	for {
		,  := .Token()
		if  != nil {
			if errors.Is(, io.EOF) {
				break
			}
			return nil, fmt.Errorf("parse paper XML: %w", )
		}
		,  := .(xml.StartElement)
		if ! || .Name.Local != "path" {
			continue
		}
		if len()&255 == 0 {
			if  := .Err();  != nil {
				return nil, 
			}
		}
		var ,  string
		for ,  := range .Attr {
			switch .Name.Local {
			case "d":
				 = .Value
			case "fill":
				 = .Value
			}
		}
		,  := paperPatternColorIndex()
		if ! ||  == "" {
			return nil, fmt.Errorf("paper path %d has unsupported fill %q or empty data", len(), )
		}
		 = append(, {: , : })
	}
	if len() != paperPatternPathCount+1 || [0]. != 6 || [0]. != paperPatternMaskPathData || [1]. != 0 {
		return nil, fmt.Errorf("paper definition topology changed: got %d paths", len())
	}
	 = [1:]

	 := &paperPatternSource{paths: make([]paperPatternPath, len())}
	 := 0
	for ,  := range  {
		if  := .Err();  != nil {
			return nil, 
		}
		,  := d2svgimport.ParsePath(, "paper pattern", ., d2svgimport.PathLimits{
			MaxBytes: len(.), MaxCommands: paperPatternCommandCount,
		})
		if  != nil {
			return nil, fmt.Errorf("paper path %d: %w", , )
		}
		if len() == 0 ||  > paperPatternCommandCount-len() {
			return nil, fmt.Errorf("paper path %d exceeds the command topology", )
		}
		.paths[] = paperPatternPath{color: ., commands: }
		 += len()
	}
	if  != paperPatternCommandCount {
		return nil, fmt.Errorf("paper command count %d, want %d", , paperPatternCommandCount)
	}
	return , nil
}

func paperPatternColorIndex( string) (uint8, bool) {
	switch  {
	case "black":
		return 0, true
	case "#EFEFEF":
		return 1, true
	case "#F5F5F5":
		return 2, true
	case "#F7F7F7":
		return 3, true
	case "#F9F9F9":
		return 4, true
	case "#FCFCFC":
		return 5, true
	case "white":
		return 6, true
	default:
		return 0, false
	}
}

func parseGrainPatternSource( context.Context,  []byte) (*grainPatternSource, error) {
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	if sourceBytesDigest() != grainPatternPNGSourceSHA256 {
		return nil, fmt.Errorf("grain definition digest does not match its asset ID")
	}
	if len() != grainPatternPNGBytes || len() < 24 ||
		!bytes.Equal([:8], []byte("\x89PNG\r\n\x1a\n")) || string([12:16]) != "IHDR" {
		return nil, fmt.Errorf("grain image is not the expected PNG")
	}
	 := int(binary.BigEndian.Uint32([16:20]))
	 := int(binary.BigEndian.Uint32([20:24]))
	if  != grainPatternPixelWidth ||  != grainPatternPixelHeight {
		return nil, fmt.Errorf("grain dimensions changed to %dx%d", , )
	}
	return &grainPatternSource{
		png: , pixelWidth: , pixelHeight: ,
		decodedBytes: int64() * int64() * 4,
	}, nil
}

func parseSketchStreakSource( context.Context,  string) ([]d2scene.PathCommand, error) {
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	if  == "" {
		return nil, fmt.Errorf("sketch streak path is empty")
	}
	,  := d2svgimport.ParsePath(, "sketch streak pattern", , d2svgimport.PathLimits{
		MaxBytes: len(), MaxCommands: sketchStreakCommandCount,
	})
	if  != nil {
		return nil, 
	}
	if len() != sketchStreakCommandCount {
		return nil, fmt.Errorf("sketch streak command count %d, want %d", len(), sketchStreakCommandCount)
	}
	return , nil
}

func sourceStringDigest( string) string {
	 := sha256.Sum256([]byte())
	return fmt.Sprintf("%x", )
}

func sourceBytesDigest( []byte) string {
	 := sha256.Sum256()
	return fmt.Sprintf("%x", )
}