/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#include <EGL/egl.h>
#include <GLES2/gl2.h>

#include <system/graphics.h>

#include "util.h"

void matrix_init_ortho(GLfloat *m, float w, float h) {
	m[0] = 2.0 / w;
	m[1] = 0.0;
	m[2] = 0.0;
	m[3] = -1.0;
	m[4] = 0.0;
	m[5] = 2.0 / h;
	m[6] = 0.0;
	m[7] = -1.0;
	m[8] = 0.0;
	m[9] = 0.0;
	m[10] = -1.0;
	m[11] = 0.0;
	m[12] = 0.0;
	m[13] = 0.0;
	m[14] = 0.0;
	m[15] = 1.0;
}

static GLuint load_shader(GLenum shaderType, const char *src) {
	GLint status = 0, len = 0;
	GLuint shader;

	if (!(shader = glCreateShader(shaderType)))
		return 0;

	glShaderSource(shader, 1, &src, NULL);
	glCompileShader(shader);
	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);

	if (status)
		return shader;

	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
	if (len) {
		char *msg = malloc(len);
		if (msg) {
			glGetShaderInfoLog(shader, len, NULL, msg);
			msg[len-1] = 0;
			fprintf(stderr, "error compiling shader:\n%s\n", msg);
			free(msg);
		}
	}
	glDeleteShader(shader);
	return 0;
}

GLuint load_program(const char *vert_src, const char *frag_src) {
	GLuint vert, frag, prog;
	GLint status = 0, len = 0;

	if (!(vert = load_shader(GL_VERTEX_SHADER, vert_src)))
		return 0;
	if (!(frag = load_shader(GL_FRAGMENT_SHADER, frag_src)))
		goto fail_frag;
	if (!(prog = glCreateProgram()))
		goto fail_prog;

	glAttachShader(prog, vert);
	glAttachShader(prog, frag);
	glLinkProgram(prog);

	glGetProgramiv(prog, GL_LINK_STATUS, &status);
	if (status)
		return prog;

	glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
	if (len) {
		char *buf = (char*) malloc(len);
		if (buf) {
			glGetProgramInfoLog(prog, len, NULL, buf);
			buf[len-1] = 0;
			fprintf(stderr, "error linking program:\n%s\n", buf);
			free(buf);
		}
	}
	glDeleteProgram(prog);
fail_prog:
	glDeleteShader(frag);
fail_frag:
	glDeleteShader(vert);
	return 0;
}

int select_config_for_window(EGLDisplay dpy, EGLint *attr,
	unsigned format, EGLConfig *config) {
	EGLint R,G,B,A;
	EGLint i, n, max;
	EGLConfig *cfg;

	switch (format) {
	case HAL_PIXEL_FORMAT_RGBA_8888:
	case HAL_PIXEL_FORMAT_BGRA_8888:
		R = G = B = A = 8;
		break;
	case HAL_PIXEL_FORMAT_RGB_565:
		R = 5; G = 6; B = 5; A = 0;
		break;
	default:
		fprintf(stderr, "unknown fb pixel format %d\n", format);
		return -1;
	}

	if (eglGetConfigs(dpy, NULL, 0, &max) == EGL_FALSE) {
		fprintf(stderr, "no EGL configurations available?!\n");
		return -1;
	}

	cfg = (EGLConfig*) malloc(sizeof(EGLConfig) * max);
	if (!cfg)
		return -1;

	if (eglChooseConfig(dpy, attr, cfg, max, &n) == EGL_FALSE) {
		fprintf(stderr, "eglChooseConfig failed\n");
		return -1;
	}

	for (i = 0; i < n; i++) {
		EGLint r,g,b,a;
		eglGetConfigAttrib(dpy, cfg[i], EGL_RED_SIZE,   &r);
		eglGetConfigAttrib(dpy, cfg[i], EGL_GREEN_SIZE, &g);
		eglGetConfigAttrib(dpy, cfg[i], EGL_BLUE_SIZE,  &b);
		eglGetConfigAttrib(dpy, cfg[i], EGL_ALPHA_SIZE, &a);
		if (r == R && g == G && b == B && a == A) {
			*config = cfg[i];
			free(cfg);
			return 0;
		}
	}

	fprintf(stderr, "cannot find matching config\n");
	free(cfg);
	return -1;
}

static struct CNativeWindow *_cnw = 0;

int egl_create(EGLDisplay *_display, EGLSurface *_surface, int *_w, int *_h) {
	EGLBoolean res;
	EGLConfig config = { 0 };
	EGLint context_attrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
	EGLint config_attrs[] = {
		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
		EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
		EGL_NONE };
	EGLint major, minor;
	EGLContext context;
	EGLSurface surface;
	EGLint w, h;
	EGLDisplay display;
	EGLNativeWindowType window;
	unsigned width, height, format;
	struct CNativeWindow *cnw;

	display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
	if (display == EGL_NO_DISPLAY)
		return -1;

	if (!(res = eglInitialize(display, &major, &minor)))
		return -1;

	fprintf(stderr, "egl version: %d.%d\n", major, minor);

	if ((cnw = cnw_create()) == 0)
		return -1;

	cnw_info(cnw, &width, &height, &format);
	window = (EGLNativeWindowType) cnw;

	if ((res = select_config_for_window(display, config_attrs, format, &config)))
		goto fail;

	surface = eglCreateWindowSurface(display, config, window, NULL);
	if (surface == EGL_NO_SURFACE)
		goto fail;

	context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrs);
	if (context == EGL_NO_CONTEXT)
		goto fail;

	if (!(res = eglMakeCurrent(display, surface, surface, context)))
		goto fail;

	eglQuerySurface(display, surface, EGL_WIDTH, &w);
	eglQuerySurface(display, surface, EGL_HEIGHT, &h);

	fprintf(stderr, "window: %d x %d\n", w, h);

	*_display = display;
	*_surface = surface;
	*_w = w;
	*_h = h;

	_cnw = cnw;
	return 0;

fail:
	cnw_destroy(cnw);
	return -1;
}

void egl_destroy(EGLDisplay display, EGLSurface surface) {
	if (_cnw) {
		eglDestroySurface(display, surface);
		eglTerminate(display);
		cnw_destroy(_cnw);
		_cnw = 0;
	}
}