// games/4s - a tetris clone
//
// Derived from Plan 9's /sys/src/games/xs.c
// http://plan9.bell-labs.com/sources/plan9/sys/src/games/xs.c
//
// Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights Reserved.
// Portions Copyright 2009 The Go Authors.  All Rights Reserved.
// Distributed under the terms of the Lucent Public License Version 1.02
// See http://plan9.bell-labs.com/plan9/license.html

/*
 * engine for 4s, 5s, etc
 */

package main

import (
	"exp/draw"
	"image"
	"log"
	"os"
	"rand"
	"time"
)

/*
Cursor whitearrow = {
	{0, 0},
	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
	 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC,
	 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
	 0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
	{0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C,
	 0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C,
	 0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C,
	 0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
};
*/

const (
	CNone   = 0
	CBounds = 1
	CPiece  = 2
	NX      = 10
	NY      = 20

	NCOL = 10

	MAXN = 5
)

var (
	N                        int
	display                  draw.Context
	screen                   draw.Image
	screenr                  draw.Rectangle
	board                    [NY][NX]byte
	rboard                   draw.Rectangle
	pscore                   draw.Point
	scoresz                  draw.Point
	pcsz                     = 32
	pos                      draw.Point
	bbr, bb2r                draw.Rectangle
	bb, bbmask, bb2, bb2mask *image.RGBA
	whitemask                image.Image
	br, br2                  draw.Rectangle
	points                   int
	dt                       int
	DY                       int
	DMOUSE                   int
	lastmx                   int
	mouse                    draw.Mouse
	newscreen                bool
	timerc                   <-chan int64
	suspc                    chan bool
	mousec                   chan draw.Mouse
	resizec                  <-chan bool
	kbdc                     chan int
	suspended                bool
	tsleep                   int
	piece                    *Piece
	pieces                   []Piece
)

type Piece struct {
	rot   int
	tx    int
	sz    draw.Point
	d     []draw.Point
	left  *Piece
	right *Piece
}

var txbits = [NCOL][32]byte{
	[32]byte{0xDD, 0xDD, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF,
		0xDD, 0xDD, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF,
		0xDD, 0xDD, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF,
		0xDD, 0xDD, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF,
	},
	[32]byte{0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
		0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
		0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
		0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
	},
	[32]byte{0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
		0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
		0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
		0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
	},
	[32]byte{0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
		0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
		0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
		0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
	},
	[32]byte{0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
		0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
		0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
		0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
	},
	[32]byte{0x22, 0x22, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00,
		0x22, 0x22, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00,
		0x22, 0x22, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00,
		0x22, 0x22, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00,
	},
	[32]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
		0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
		0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
		0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
	},
	[32]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
		0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
		0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
		0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
	},
	[32]byte{0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
		0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
		0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
		0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
	},
	[32]byte{0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33,
		0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33,
		0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33,
		0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33,
	},
}

var txpix = [NCOL]draw.Color{
	draw.Yellow,            /* yellow */
	draw.Cyan,              /* cyan */
	draw.Green,             /* lime green */
	draw.GreyBlue,          /* slate */
	draw.Red,               /* red */
	draw.GreyGreen,         /* olive green */
	draw.Blue,              /* blue */
	draw.Color(0xFF55AAFF), /* pink */
	draw.Color(0xFFAAFFFF), /* lavender */
	draw.Color(0xBB005DFF), /* maroon */
}

func movemouse() int {
	//mouse.draw.Point = draw.Pt(rboard.Min.X + rboard.Dx()/2, rboard.Min.Y + rboard.Dy()/2);
	//moveto(mousectl, mouse.Xy);
	return mouse.X
}

func warp(p draw.Point, x int) int {
	if !suspended && piece != nil {
		x = pos.X + piece.sz.X*pcsz/2
		if p.Y < rboard.Min.Y {
			p.Y = rboard.Min.Y
		}
		if p.Y >= rboard.Max.Y {
			p.Y = rboard.Max.Y - 1
		}
		//moveto(mousectl, draw.Pt(x, p.Y));
	}
	return x
}

func initPieces() {
	for i := range pieces {
		p := &pieces[i]
		if p.rot == 3 {
			p.right = &pieces[i-3]
		} else {
			p.right = &pieces[i+1]
		}
		if p.rot == 0 {
			p.left = &pieces[i+3]
		} else {
			p.left = &pieces[i-1]
		}
	}
}

func collide(pt draw.Point, p *Piece) bool {
	pt.X = (pt.X - rboard.Min.X) / pcsz
	pt.Y = (pt.Y - rboard.Min.Y) / pcsz
	for _, q := range p.d {
		pt.X += q.X
		pt.Y += q.Y
		if pt.X < 0 || pt.X >= NX || pt.Y < 0 || pt.Y >= NY {
			return true
			continue
		}
		if board[pt.Y][pt.X] != 0 {
			return true
		}
	}
	return false
}

func collider(pt, pmax draw.Point) bool {
	pi := (pt.X - rboard.Min.X) / pcsz
	pj := (pt.Y - rboard.Min.Y) / pcsz
	n := pmax.X / pcsz
	m := pmax.Y/pcsz + 1
	for i := pi; i < pi+n && i < NX; i++ {
		for j := pj; j < pj+m && j < NY; j++ {
			if board[j][i] != 0 {
				return true
			}
		}
	}
	return false
}

func setpiece(p *Piece) {
	draw.Draw(bb, bbr, draw.White, draw.ZP)
	draw.Draw(bbmask, bbr, draw.Transparent, draw.ZP)
	br = draw.Rect(0, 0, 0, 0)
	br2 = br
	piece = p
	if p == nil {
		return
	}
	var op draw.Point
	var r draw.Rectangle
	r.Min = bbr.Min
	for i, pt := range p.d {
		r.Min.X += pt.X * pcsz
		r.Min.Y += pt.Y * pcsz
		r.Max.X = r.Min.X + pcsz
		r.Max.Y = r.Min.Y + pcsz
		if i == 0 {
			draw.Draw(bb, r, draw.Black, draw.ZP)
			draw.Draw(bb, r.Inset(1), txpix[piece.tx], draw.ZP)
			draw.Draw(bbmask, r, draw.Opaque, draw.ZP)
			op = r.Min
		} else {
			draw.Draw(bb, r, bb, op)
			draw.Draw(bbmask, r, bbmask, op)
		}
		if br.Max.X < r.Max.X {
			br.Max.X = r.Max.X
		}
		if br.Max.Y < r.Max.Y {
			br.Max.Y = r.Max.Y
		}
	}
	br.Max = br.Max.Sub(bbr.Min)
	delta := draw.Pt(0, DY)
	br2.Max = br.Max.Add(delta)
	r = br.Add(bb2r.Min)
	r2 := br2.Add(bb2r.Min)
	draw.Draw(bb2, r2, draw.White, draw.ZP)
	draw.Draw(bb2, r.Add(delta), bb, bbr.Min)
	draw.Draw(bb2mask, r2, draw.Transparent, draw.ZP)
	draw.DrawMask(bb2mask, r, draw.Opaque, bbr.Min, bbmask, draw.ZP, draw.Over)
	draw.DrawMask(bb2mask, r.Add(delta), draw.Opaque, bbr.Min, bbmask, draw.ZP, draw.Over)
}

func drawpiece() {
	draw.DrawMask(screen, br.Add(pos), bb, bbr.Min, bbmask, draw.ZP, draw.Over)
	if suspended {
		draw.DrawMask(screen, br.Add(pos), draw.White, draw.ZP, whitemask, draw.ZP, draw.Over)
	}
}

func undrawpiece() {
	var mask image.Image
	if collider(pos, br.Max) {
		mask = bbmask
	}
	draw.DrawMask(screen, br.Add(pos), draw.White, bbr.Min, mask, bbr.Min, draw.Over)
}

func rest() {
	pt := pos.Sub(rboard.Min).Div(pcsz)
	for _, p := range piece.d {
		pt.X += p.X
		pt.Y += p.Y
		board[pt.Y][pt.X] = byte(piece.tx + 16)
	}
}

func canfit(p *Piece) bool {
	var dx = [...]int{0, -1, 1, -2, 2, -3, 3, 4, -4}
	j := N + 1
	if j >= 4 {
		j = p.sz.X
		if j < p.sz.Y {
			j = p.sz.Y
		}
		j = 2*j - 1
	}
	for i := 0; i < j; i++ {
		var z draw.Point
		z.X = pos.X + dx[i]*pcsz
		z.Y = pos.Y
		if !collide(z, p) {
			z.Y = pos.Y + pcsz - 1
			if !collide(z, p) {
				undrawpiece()
				pos.X = z.X
				return true
			}
		}
	}
	return false
}

func score(p int) {
	points += p
	//	snprint(buf, sizeof(buf), "%.6ld", points);
	//	draw.Draw(screen, draw.Rpt(pscore, pscore.Add(scoresz)), draw.White, draw.ZP);
	//	string(screen, pscore, draw.Black, draw.ZP, font, buf);
}

func drawsq(b draw.Image, p draw.Point, ptx int) {
	var r draw.Rectangle
	r.Min = p
	r.Max.X = r.Min.X + pcsz
	r.Max.Y = r.Min.Y + pcsz
	draw.Draw(b, r, draw.Black, draw.ZP)
	draw.Draw(b, r.Inset(1), txpix[ptx], draw.ZP)
}

func drawboard() {
	draw.Border(screen, rboard.Inset(-2), 2, draw.Black, draw.ZP)
	draw.Draw(screen, draw.Rect(rboard.Min.X, rboard.Min.Y-2, rboard.Max.X, rboard.Min.Y),
		draw.White, draw.ZP)
	for i := 0; i < NY; i++ {
		for j := 0; j < NX; j++ {
			if board[i][j] != 0 {
				drawsq(screen, draw.Pt(rboard.Min.X+j*pcsz, rboard.Min.Y+i*pcsz), int(board[i][j]-16))
			}
		}
	}
	score(0)
	if suspended {
		draw.DrawMask(screen, screenr, draw.White, draw.ZP, whitemask, draw.ZP, draw.Over)
	}
}

func choosepiece() {
	for {
		i := rand.Intn(len(pieces))
		setpiece(&pieces[i])
		pos = rboard.Min
		pos.X += rand.Intn(NX) * pcsz
		if !collide(draw.Pt(pos.X, pos.Y+pcsz-DY), piece) {
			break
		}
	}
	drawpiece()
	display.FlushImage()
}

func movepiece() bool {
	var mask image.Image
	if collide(draw.Pt(pos.X, pos.Y+pcsz), piece) {
		return false
	}
	if collider(pos, br2.Max) {
		mask = bb2mask
	}
	draw.DrawMask(screen, br2.Add(pos), bb2, bb2r.Min, mask, bb2r.Min, draw.Over)
	pos.Y += DY
	display.FlushImage()
	return true
}

func suspend(s bool) {
	suspended = s
	/*
		if suspended {
			setcursor(mousectl, &whitearrow);
		} else {
			setcursor(mousectl, nil);
		}
	*/
	if !suspended {
		drawpiece()
	}
	drawboard()
	display.FlushImage()
}

func pause(t int) {
	display.FlushImage()
	for {
		select {
		case s := <-suspc:
			if !suspended && s {
				suspend(true)
			} else if suspended && !s {
				suspend(false)
				lastmx = warp(mouse.Point, lastmx)
			}
		case <-timerc:
			if suspended {
				break
			}
			t -= tsleep
			if t < 0 {
				return
			}
		case <-resizec:
			//redraw(true);
		case mouse = <-mousec:
		case <-kbdc:
		}
	}
}

func horiz() bool {
	var lev [MAXN]int
	h := 0
	for i := 0; i < NY; i++ {
		for j := 0; board[i][j] != 0; j++ {
			if j == NX-1 {
				lev[h] = i
				h++
				break
			}
		}
	}
	if h == 0 {
		return false
	}
	r := rboard
	newscreen = false
	for j := 0; j < h; j++ {
		r.Min.Y = rboard.Min.Y + lev[j]*pcsz
		r.Max.Y = r.Min.Y + pcsz
		draw.DrawMask(screen, r, draw.White, draw.ZP, whitemask, draw.ZP, draw.Over)
		display.FlushImage()
	}
	PlaySound(whoosh)
	for i := 0; i < 3; i++ {
		pause(250)
		if newscreen {
			drawboard()
			break
		}
		for j := 0; j < h; j++ {
			r.Min.Y = rboard.Min.Y + lev[j]*pcsz
			r.Max.Y = r.Min.Y + pcsz
			draw.DrawMask(screen, r, draw.White, draw.ZP, whitemask, draw.ZP, draw.Over)
		}
		display.FlushImage()
	}
	r = rboard
	for j := 0; j < h; j++ {
		i := NY - lev[j] - 1
		score(250 + 10*i*i)
		r.Min.Y = rboard.Min.Y
		r.Max.Y = rboard.Min.Y + lev[j]*pcsz
		draw.Draw(screen, r.Add(draw.Pt(0, pcsz)), screen, r.Min)
		r.Max.Y = rboard.Min.Y + pcsz
		draw.Draw(screen, r, draw.White, draw.ZP)
		for k := lev[j] - 1; k >= 0; k-- {
			board[k+1] = board[k]
		}
		board[0] = [NX]byte{}
	}
	display.FlushImage()
	return true
}

func mright() {
	if !collide(draw.Pt(pos.X+pcsz, pos.Y), piece) &&
		!collide(draw.Pt(pos.X+pcsz, pos.Y+pcsz-DY), piece) {
		undrawpiece()
		pos.X += pcsz
		drawpiece()
		display.FlushImage()
	}
}

func mleft() {
	if !collide(draw.Pt(pos.X-pcsz, pos.Y), piece) &&
		!collide(draw.Pt(pos.X-pcsz, pos.Y+pcsz-DY), piece) {
		undrawpiece()
		pos.X -= pcsz
		drawpiece()
		display.FlushImage()
	}
}

func rright() {
	if canfit(piece.right) {
		setpiece(piece.right)
		drawpiece()
		display.FlushImage()
	}
}

func rleft() {
	if canfit(piece.left) {
		setpiece(piece.left)
		drawpiece()
		display.FlushImage()
	}
}

var fusst = 0

func drop(f bool) bool {
	if f {
		score(5 * (rboard.Max.Y - pos.Y) / pcsz)
		for movepiece() {
		}
	}
	fusst = 0
	rest()
	if pos.Y == rboard.Min.Y && !horiz() {
		return true
	}
	horiz()
	setpiece(nil)
	pause(1500)
	choosepiece()
	lastmx = warp(mouse.Point, lastmx)
	return false
}

func play() {
	var om draw.Mouse
	dt = 64
	lastmx = -1
	lastmx = movemouse()
	choosepiece()
	lastmx = warp(mouse.Point, lastmx)
	for {
		select {
		case mouse = <-mousec:
			if suspended {
				om = mouse
				break
			}
			if lastmx < 0 {
				lastmx = mouse.X
			}
			if mouse.X > lastmx+DMOUSE {
				mright()
				lastmx = mouse.X
			}
			if mouse.X < lastmx-DMOUSE {
				mleft()
				lastmx = mouse.X
			}
			if mouse.Buttons&^om.Buttons&1 == 1 {
				rleft()
			}
			if mouse.Buttons&^om.Buttons&2 == 2 {
				if drop(true) {
					return
				}
			}
			if mouse.Buttons&^om.Buttons&4 == 4 {
				rright()
			}
			om = mouse

		case s := <-suspc:
			if !suspended && s {
				suspend(true)
			} else if suspended && !s {
				suspend(false)
				lastmx = warp(mouse.Point, lastmx)
			}

		case <-resizec:
			//redraw(true);

		case r := <-kbdc:
			if suspended {
				break
			}
			switch r {
			case 'f', ';':
				mright()
			case 'a', 'j':
				mleft()
			case 'd', 'l':
				rright()
			case 's', 'k':
				rleft()
			case ' ':
				if drop(true) {
					return
				}
			}

		case <-timerc:
			if suspended {
				break
			}
			dt -= tsleep
			if dt < 0 {
				i := 1
				dt = 16 * (points + rand.Intn(10000) - 5000) / 10000
				if dt >= 32 {
					i += (dt - 32) / 16
					dt = 32
				}
				dt = 52 - dt
				for ; i > 0; i-- {
					if movepiece() {
						continue
					}
					fusst++
					if fusst == 40 {
						if drop(false) {
							return
						}
						break
					}
				}
			}
		}
	}
}

func suspproc() {
	mc := display.MouseChan()
	kc := display.KeyboardChan()

	s := false
	for {
		select {
		case mouse = <-mc:
			mousec <- mouse
		case r := <-kc:
			switch r {
			case 'q', 'Q', 0x04, 0x7F:
				os.Exit(0)
			default:
				if s {
					s = false
					suspc <- s
					break
				}
				switch r {
				case 'z', 'Z', 'p', 'P', 0x1B:
					s = true
					suspc <- s
				default:
					kbdc <- r
				}
			}
		}
	}
}

func redraw(new bool) {
	//	if new && getwindow(display, Refmesg) < 0 {
	//		sysfatal("can't reattach to window");
	//	}
	r := draw.Rect(0, 0, screen.Width(), screen.Height())
	pos.X = (pos.X - rboard.Min.X) / pcsz
	pos.Y = (pos.Y - rboard.Min.Y) / pcsz
	dx := r.Max.X - r.Min.X
	dy := r.Max.Y - r.Min.Y - 2*32
	DY = dx / NX
	if DY > dy/NY {
		DY = dy / NY
	}
	DY /= 8
	if DY > 4 {
		DY = 4
	}
	pcsz = DY * 8
	DMOUSE = pcsz / 3
	if pcsz < 8 {
		log.Exitf("screen too small: %d", pcsz)
	}
	rboard = screenr
	rboard.Min.X += (dx - pcsz*NX) / 2
	rboard.Min.Y += (dy-pcsz*NY)/2 + 32
	rboard.Max.X = rboard.Min.X + NX*pcsz
	rboard.Max.Y = rboard.Min.Y + NY*pcsz
	pscore.X = rboard.Min.X + 8
	pscore.Y = rboard.Min.Y - 32
	//	scoresz = stringsize(font, "000000");
	pos.X = pos.X*pcsz + rboard.Min.X
	pos.Y = pos.Y*pcsz + rboard.Min.Y
	bbr = draw.Rect(0, 0, N*pcsz, N*pcsz)
	bb = image.NewRGBA(bbr.Max.X, bbr.Max.Y)
	bbmask = image.NewRGBA(bbr.Max.X, bbr.Max.Y) // actually just a bitmap
	bb2r = draw.Rect(0, 0, N*pcsz, N*pcsz+DY)
	bb2 = image.NewRGBA(bb2r.Dx(), bb2r.Dy())
	bb2mask = image.NewRGBA(bb2r.Dx(), bb2r.Dy()) // actually just a bitmap
	draw.Draw(screen, screenr, draw.White, draw.ZP)
	drawboard()
	setpiece(piece)
	if piece != nil {
		drawpiece()
	}
	lastmx = movemouse()
	newscreen = true
	display.FlushImage()
}

func quitter(c <-chan bool) {
	<-c
	os.Exit(0)
}

func Play(pp []Piece, ctxt draw.Context) {
	display = ctxt
	screen = ctxt.Screen()
	screenr = draw.Rect(0, 0, screen.Width(), screen.Height())
	pieces = pp
	N = len(pieces[0].d)
	initPieces()
	rand.Seed(int64(time.Nanoseconds() % (1e9 - 1)))
	whitemask = draw.White.SetAlpha(0x7F)
	tsleep = 50
	timerc = time.Tick(int64(tsleep/2) * 1e6)
	suspc = make(chan bool)
	mousec = make(chan draw.Mouse)
	resizec = ctxt.ResizeChan()
	kbdc = make(chan int)
	go quitter(ctxt.QuitChan())
	go suspproc()
	points = 0
	redraw(false)
	play()
}
