package screen

import (
	"image"
	"image/color"
	"image/png"
	"os"
)

/*
References:
 - "Understanding the Apple II", http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
 - "Apple II Reference Manual"
 - "More Colors for your Apple", https://archive.org/details/byte-magazine-1979-06/page/n61
*/

const (
	// ScreenModeGreen to render as a green phosphor monitor
	ScreenModeGreen = iota
	// ScreenModePlain to render in color with filled areas
	ScreenModePlain
	//ScreenModeNTSC shows spaces between pixels
	ScreenModeNTSC
)

// Snapshot the currently visible screen
func Snapshot(vs VideoSource, screenMode int) *image.RGBA {
	videoMode := vs.GetCurrentVideoMode()
	snap := snapshotByMode(vs, videoMode, screenMode)

	if screenMode != ScreenModePlain && snap.Bounds().Dy() == hiResHeight {
		// Apply the filter to regular CRT snapshots with 192 lines. Not to SHR
		snap = linesSeparatedFilter(snap)
	}

	return snap
}

// SnapshotPaletted, snapshot of the currently visible screen as a paletted image
func SnapshotPaletted(vs VideoSource, screenMode int) *image.Paletted {
	img := Snapshot(vs, screenMode)
	return palletedFilter(img)
}

// Color for typical Apple ][ period green P1 phosphor monitors
// See: https://superuser.com/questions/361297/what-colour-is-the-dark-green-on-old-fashioned-green-screen-computer-displays
var greenPhosphorColor = color.RGBA{65, 255, 0, 255}

func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGBA {
	videoBase := videoMode & VideoBaseMask
	mixMode := videoMode & VideoMixTextMask
	isSecondPage := (videoMode & VideoSecondPage) != 0
	isAltText := (videoMode & VideoAltText) != 0
	isRGBCard := (videoMode & VideoRGBCard) != 0
	shiftSupported := (videoMode & VideoFourColors) == 0

	var lightColor color.Color = color.White
	if screenMode == ScreenModeGreen {
		lightColor = greenPhosphorColor
	}

	applyNTSCFilter := screenMode != ScreenModeGreen
	var snap *image.RGBA
	var ntscMask *image.Alpha
	switch videoBase {
	case VideoText40:
		snap = snapshotText40(vs, isSecondPage, isAltText, lightColor)
		applyNTSCFilter = false
	case VideoText80:
		snap = snapshotText80(vs, isSecondPage, isAltText, lightColor)
		applyNTSCFilter = false
	case VideoText40RGB:
		snap = snapshotText40RGB(vs, isSecondPage, isAltText)
		applyNTSCFilter = false
	case VideoGR:
		snap = snapshotLoRes(vs, isSecondPage, lightColor)
	case VideoDGR:
		snap = snapshotMeRes(vs, isSecondPage, lightColor)
	case VideoHGR:
		snap = snapshotHiRes(vs, isSecondPage, lightColor, shiftSupported)
	case VideoDHGR:
		snap, _ = snapshotDoubleHiRes(vs, isSecondPage, false /*isRGBMixMode*/, lightColor)
	case VideoMono560:
		snap, _ = snapshotDoubleHiRes(vs, isSecondPage, false /*isRGBMixMode*/, lightColor)
		applyNTSCFilter = false
	case VideoRGBMix:
		snap, ntscMask = snapshotDoubleHiRes(vs, isSecondPage, true /*isRGBMixMode*/, lightColor)
	case VideoRGB160:
		snap = snapshotDoubleHiRes160(vs, isSecondPage, lightColor)
	case VideoSHR:
		snap = snapshotSuperHiRes(vs)
		applyNTSCFilter = false
	case VideoVidex:
		snap = vs.GetCardImage(lightColor)
		applyNTSCFilter = false
	}

	if applyNTSCFilter {
		snap = filterNTSCColor(snap, ntscMask, screenMode)
	}

	if mixMode != 0 {
		var bottom *image.RGBA
		applyNTSCFilter := screenMode != ScreenModeGreen && !isRGBCard
		switch mixMode {
		case VideoMixText40:
			bottom = snapshotText40(vs, isSecondPage, isAltText, lightColor)
		case VideoMixText80:
			bottom = snapshotText80(vs, isSecondPage, isAltText, lightColor)
		case VideoMixText40RGB:
			bottom = snapshotText40RGB(vs, isSecondPage, isAltText)
			applyNTSCFilter = false
		}
		if applyNTSCFilter {
			bottom = filterNTSCColor(bottom, ntscMask, screenMode)
		}
		snap = mixSnapshots(snap, bottom)
	}

	return snap
}

func mixSnapshots(top, bottom *image.RGBA) *image.RGBA {
	bottomWidth := bottom.Bounds().Dx()

	// Copy bottom's bottom on top's bottom
	for y := hiResHeightMixed; y < hiResHeight; y++ {
		for x := 0; x < bottomWidth; x++ {
			c := bottom.At(x, y)
			top.Set(x, y, c)
		}
	}

	return top
}

// SaveSnapshot saves a snapshot of the screen to a png file
func SaveSnapshot(vs VideoSource, screenMode int, filename string) error {
	img := Snapshot(vs, screenMode)
	img = squarishPixelsFilter(img)

	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	png.Encode(f, img)
	return nil
}

func squarishPixelsFilter(in *image.RGBA) *image.RGBA {
	b := in.Bounds()
	factor := 1200 / b.Dx()
	size := image.Rect(0, 0, factor*b.Dx(), b.Dy())
	out := image.NewRGBA(size)
	for x := b.Min.X; x < b.Max.X; x++ {
		for y := b.Min.Y; y < b.Max.Y; y++ {
			c := in.At(x, y)
			for i := 0; i < factor; i++ {
				out.Set(factor*x+i, y, c)
			}
		}
	}
	return out
}

func linesSeparatedFilter(in *image.RGBA) *image.RGBA {
	b := in.Bounds()
	size := image.Rect(0, 0, b.Dx(), 4*b.Dy())
	out := image.NewRGBA(size)
	for y := b.Min.Y; y < b.Max.Y; y++ {
		for x := b.Min.X; x < b.Max.X; x++ {
			c := in.At(x, y)
			out.Set(x, 4*y, c)
			out.Set(x, 4*y+1, c)
			out.Set(x, 4*y+2, c)
			out.Set(x, 4*y+3, color.Black)
		}
	}
	return out
}

func palletedFilter(in *image.RGBA) *image.Paletted {
	bounds := in.Bounds()
	outBounds := image.Rect(0, 0, bounds.Dx()*2, bounds.Dy())
	palette := []color.Color{color.Black, color.White, greenPhosphorColor}
	palette = append(palette, ntscColorMap[:]...)
	palette = append(palette, attenuatedColorMap[:]...)
	paletted := image.NewPaletted(outBounds, palette)

	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
		for x := bounds.Min.X; x < bounds.Max.X; x++ {
			c := in.At(x, y)
			paletted.Set(x*2, y, c)
			paletted.Set(x*2+1, y, c)
		}
	}
	return paletted
}
