// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gldriver

import (
	"fmt"
	"image"
	"sync"

	"golang.org/x/exp/shiny/screen"
	"golang.org/x/mobile/gl"
)

var theScreen = &screenImpl{
	windows: make(map[uintptr]*windowImpl),
}

type screenImpl struct {
	texture struct {
		program gl.Program
		pos     gl.Attrib
		mvp     gl.Uniform
		uvp     gl.Uniform
		inUV    gl.Attrib
		sample  gl.Uniform
		quad    gl.Buffer
	}
	fill struct {
		program gl.Program
		pos     gl.Attrib
		mvp     gl.Uniform
		color   gl.Uniform
		quad    gl.Buffer
	}

	mu      sync.Mutex
	windows map[uintptr]*windowImpl
}

func (s *screenImpl) NewBuffer(size image.Point) (retBuf screen.Buffer, retErr error) {
	m := image.NewRGBA(image.Rectangle{Max: size})
	return &bufferImpl{
		buf:  m.Pix,
		rgba: *m,
		size: size,
	}, nil
}

func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) {
	// TODO: can we compile these programs eagerly instead of lazily?

	// Find a GL context for this texture.
	// TODO: this might be correct. Some GL objects can be shared
	// across contexts. But this needs a review of the spec to make
	// sure it's correct, and some testing would be nice.
	var w *windowImpl

	s.mu.Lock()
	for _, window := range s.windows {
		w = window
		break
	}
	s.mu.Unlock()

	if w == nil {
		return nil, fmt.Errorf("gldriver: no window available")
	}

	w.glctxMu.Lock()
	defer w.glctxMu.Unlock()
	glctx := w.glctx
	if glctx == nil {
		return nil, fmt.Errorf("gldriver: no GL context available")
	}

	if !glctx.IsProgram(s.texture.program) {
		p, err := compileProgram(glctx, textureVertexSrc, textureFragmentSrc)
		if err != nil {
			return nil, err
		}
		s.texture.program = p
		s.texture.pos = glctx.GetAttribLocation(p, "pos")
		s.texture.mvp = glctx.GetUniformLocation(p, "mvp")
		s.texture.uvp = glctx.GetUniformLocation(p, "uvp")
		s.texture.inUV = glctx.GetAttribLocation(p, "inUV")
		s.texture.sample = glctx.GetUniformLocation(p, "sample")
		s.texture.quad = glctx.CreateBuffer()

		glctx.BindBuffer(gl.ARRAY_BUFFER, s.texture.quad)
		glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW)
	}

	t := &textureImpl{
		w:    w,
		id:   glctx.CreateTexture(),
		size: size,
	}

	glctx.BindTexture(gl.TEXTURE_2D, t.id)
	glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size.X, size.Y, gl.RGBA, gl.UNSIGNED_BYTE, nil)
	glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

	return t, nil
}

func optsSize(opts *screen.NewWindowOptions) (width, height int) {
	width, height = 1024, 768
	if opts != nil {
		if opts.Width > 0 {
			width = opts.Width
		}
		if opts.Height > 0 {
			height = opts.Height
		}
	}
	return width, height
}

func (s *screenImpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
	id, err := newWindow(opts)
	if err != nil {
		return nil, err
	}
	w := &windowImpl{
		s:           s,
		id:          id,
		publish:     make(chan struct{}),
		publishDone: make(chan screen.PublishResult),
		drawDone:    make(chan struct{}),
	}
	initWindow(w)

	s.mu.Lock()
	s.windows[id] = w
	s.mu.Unlock()

	if useLifecycler {
		w.lifecycler.SendEvent(w, nil)
	}

	showWindow(w)

	return w, nil
}
