/*
 * Copyright (c) 2011-2013 Research In Motion Limited.
 *
 * 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 <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/keycodes.h>
#include <time.h>

#include "bbutil.h"

EGLDisplay egl_disp;
EGLSurface egl_surf;

static EGLConfig egl_conf;
static EGLContext egl_ctx;

static screen_context_t screen_ctx;
static screen_window_t screen_win;
static screen_display_t screen_disp;


static void
bbutil_egl_perror(const char *msg) {
    static const char *errmsg[] = {
        "function succeeded",
        "EGL is not initialized, or could not be initialized, for the specified display",
        "cannot access a requested resource",
        "failed to allocate resources for the requested operation",
        "an unrecognized attribute or attribute value was passed in an attribute list",
        "an EGLConfig argument does not name a valid EGLConfig",
        "an EGLContext argument does not name a valid EGLContext",
        "the current surface of the calling thread is no longer valid",
        "an EGLDisplay argument does not name a valid EGLDisplay",
        "arguments are inconsistent",
        "an EGLNativePixmapType argument does not refer to a valid native pixmap",
        "an EGLNativeWindowType argument does not refer to a valid native window",
        "one or more argument values are invalid",
        "an EGLSurface argument does not name a valid surface configured for rendering",
        "a power management event has occurred",
    };

    fprintf(stderr, "%s: %s\n", msg, errmsg[eglGetError() - EGL_SUCCESS]);
}
EGLConfig bbutil_choose_config(EGLDisplay egl_disp, enum RENDERING_API api) {
    EGLConfig egl_conf = (EGLConfig)0;
    EGLConfig *egl_configs;
    EGLint egl_num_configs;
    EGLint val;
    EGLBoolean rc;
    EGLint i;

    rc = eglGetConfigs(egl_disp, NULL, 0, &egl_num_configs);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglGetConfigs");
        return egl_conf;
    }
    if (egl_num_configs == 0) {
        fprintf(stderr, "eglGetConfigs: could not find a configuration\n");
        return egl_conf;
    }

    egl_configs = malloc(egl_num_configs * sizeof(*egl_configs));
    if (egl_configs == NULL) {
        fprintf(stderr, "could not allocate memory for %d EGL configs\n", egl_num_configs);
        return egl_conf;
    }

    rc = eglGetConfigs(egl_disp, egl_configs,
        egl_num_configs, &egl_num_configs);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglGetConfigs");
        free(egl_configs);
        return egl_conf;
    }

    for (i = 0; i < egl_num_configs; i++) {
        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_SURFACE_TYPE, &val);
        if (!(val & EGL_WINDOW_BIT)) {
            continue;
        }

        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_RENDERABLE_TYPE, &val);
        if (!(val & api)) {
        	continue;
        }

        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_DEPTH_SIZE, &val);
        if ((api & (GL_ES_1|GL_ES_2)) && (val == 0)) {
            continue;
        }

        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_RED_SIZE, &val);
        if (val != 8) {
            continue;
        }
        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_GREEN_SIZE, &val);
        if (val != 8) {
            continue;
        }

        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_BLUE_SIZE, &val);
        if (val != 8) {
            continue;
        }

        eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_BUFFER_SIZE, &val);
        if (val != 32) {
            continue;
        }

        egl_conf = egl_configs[i];
        break;
    }

    free(egl_configs);

    if (egl_conf == (EGLConfig)0) {
        fprintf(stderr, "bbutil_choose_config: could not find a matching configuration\n");
    }

    return egl_conf;
}

int
bbutil_init_egl(screen_context_t ctx, enum RENDERING_API api) {
    int usage;
    int format = SCREEN_FORMAT_RGBX8888;
    int nbuffers = 2;
    EGLint interval = 1;
    int rc;
    EGLint attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };

    if (api == GL_ES_1) {
		usage = SCREEN_USAGE_OPENGL_ES1 | SCREEN_USAGE_ROTATION;
    } else if (api == GL_ES_2) {
		usage = SCREEN_USAGE_OPENGL_ES2 | SCREEN_USAGE_ROTATION;
    } else if (api == VG) {
		usage = SCREEN_USAGE_OPENVG | SCREEN_USAGE_ROTATION;
    } else {
    	fprintf(stderr, "invalid api setting\n");
    	return EXIT_FAILURE;
    }

    //Simple egl initialization
    screen_ctx = ctx;

    egl_disp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (egl_disp == EGL_NO_DISPLAY) {
        bbutil_egl_perror("eglGetDisplay");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = eglInitialize(egl_disp, NULL, NULL);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglInitialize");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    if ((api == GL_ES_1) || (api == GL_ES_2)) {
    	rc = eglBindAPI(EGL_OPENGL_ES_API);
    } else if (api == VG) {
    	rc = eglBindAPI(EGL_OPENVG_API);
    }

    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglBindApi");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

	egl_conf = bbutil_choose_config(egl_disp, api);
    if (egl_conf == (EGLConfig)0) {
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    if (api == GL_ES_2) {
    	egl_ctx = eglCreateContext(egl_disp, egl_conf, EGL_NO_CONTEXT, attributes);
    } else {
    	egl_ctx = eglCreateContext(egl_disp, egl_conf, EGL_NO_CONTEXT, NULL);
    }

    if (egl_ctx == EGL_NO_CONTEXT) {
        bbutil_egl_perror("eglCreateContext");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = screen_create_window(&screen_win, screen_ctx);
    if (rc) {
        perror("screen_create_window");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_FORMAT, &format);
    if (rc) {
        perror("screen_set_window_property_iv(SCREEN_PROPERTY_FORMAT)");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &usage);
    if (rc) {
        perror("screen_set_window_property_iv(SCREEN_PROPERTY_USAGE)");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

	rc = screen_get_window_property_pv(screen_win, SCREEN_PROPERTY_DISPLAY, (void **)&screen_disp);
	if (rc) {
		perror("screen_get_window_property_pv");
		bbutil_terminate();
		return EXIT_FAILURE;
	}

	int screen_resolution[2];

	rc = screen_get_display_property_iv(screen_disp, SCREEN_PROPERTY_SIZE, screen_resolution);
	if (rc) {
		perror("screen_get_display_property_iv");
		bbutil_terminate();
		return EXIT_FAILURE;
	}

	int angle = atoi(getenv("ORIENTATION"));

	screen_display_mode_t screen_mode;
	rc = screen_get_display_property_pv(screen_disp, SCREEN_PROPERTY_MODE, (void**)&screen_mode);
	if (rc) {
		perror("screen_get_display_property_pv");
		bbutil_terminate();
		return EXIT_FAILURE;
	}

	int size[2];
	rc = screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, size);
	if (rc) {
		perror("screen_get_window_property_iv");
		bbutil_terminate();
		return EXIT_FAILURE;
	}

	int buffer_size[2] = {size[0], size[1]};

	if ((angle == 0) || (angle == 180)) {
		if (((screen_mode.width > screen_mode.height) && (size[0] < size[1])) ||
			((screen_mode.width < screen_mode.height) && (size[0] > size[1]))) {
				buffer_size[1] = size[0];
				buffer_size[0] = size[1];
		}
	} else if ((angle == 90) || (angle == 270)){
		if (((screen_mode.width > screen_mode.height) && (size[0] > size[1])) ||
			((screen_mode.width < screen_mode.height && size[0] < size[1]))) {
				buffer_size[1] = size[0];
				buffer_size[0] = size[1];
		}
	} else {
		 fprintf(stderr, "Navigator returned an unexpected orientation angle.\n");
		 bbutil_terminate();
		 return EXIT_FAILURE;
	}

	rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, buffer_size);
	if (rc) {
		perror("screen_set_window_property_iv");
		bbutil_terminate();
		return EXIT_FAILURE;
	}

	rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_ROTATION, &angle);
	if (rc) {
		perror("screen_set_window_property_iv");
		bbutil_terminate();
		return EXIT_FAILURE;
	}

    rc = screen_create_window_buffers(screen_win, nbuffers);
    if (rc) {
        perror("screen_create_window_buffers");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

	rc = screen_create_window_group(screen_win, get_window_group_id());
	if (rc) {
		perror("screen_create_window_group");
		bbutil_terminate();
		return EXIT_FAILURE;
	}
	/* if (screen_create_window_group(screen_win, get_window_group_id()) != 0) goto fail; */

	int idle_mode = SCREEN_IDLE_MODE_KEEP_AWAKE;
	screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_IDLE_MODE, &idle_mode);

    egl_surf = eglCreateWindowSurface(egl_disp, egl_conf, screen_win, NULL);
    if (egl_surf == EGL_NO_SURFACE) {
        bbutil_egl_perror("eglCreateWindowSurface");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = eglMakeCurrent(egl_disp, egl_surf, egl_surf, egl_ctx);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglMakeCurrent");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    rc = eglSwapInterval(egl_disp, interval);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglSwapInterval");
        bbutil_terminate();
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

int
bbutil_init_gl2d() {
#if 0
    EGLint surface_width, surface_height;

    if ((egl_disp == EGL_NO_DISPLAY) || (egl_surf == EGL_NO_SURFACE) ){
    	return EXIT_FAILURE;
    }

	eglQuerySurface(egl_disp, egl_surf, EGL_WIDTH, &surface_width);
    eglQuerySurface(egl_disp, egl_surf, EGL_HEIGHT, &surface_height);

    glShadeModel(GL_SMOOTH);

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

    glViewport(0, 0, surface_width, surface_height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrthof(0.0f, (float)(surface_width) / (float)(surface_height), 0.0f, 1.0f, -1.0f, 1.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
#endif

    return EXIT_SUCCESS;
}

int
bbutil_init(screen_context_t ctx, enum RENDERING_API api) {
	if (EXIT_SUCCESS != bbutil_init_egl(ctx, api)) {
		return EXIT_FAILURE;
	}

	if ((GL_ES_1 == api) && (EXIT_SUCCESS != bbutil_init_gl2d())) {
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

int bbutil_is_flipped() {

	int ret;
	screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_FLIP, &ret);
	return ret;
};

int bbutil_get_rotation() {

	int ret;
	screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_ROTATION, &ret);
	return ret;
};


void
bbutil_terminate() {
    //Typical EGL cleanup
	if (egl_disp != EGL_NO_DISPLAY) {
	    eglMakeCurrent(egl_disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
	    if (egl_surf != EGL_NO_SURFACE) {
	        eglDestroySurface(egl_disp, egl_surf);
	        egl_surf = EGL_NO_SURFACE;
	    }
	    if (egl_ctx != EGL_NO_CONTEXT) {
	        eglDestroyContext(egl_disp, egl_ctx);
	        egl_ctx = EGL_NO_CONTEXT;
	    }
	    if (screen_win != NULL) {
	        screen_destroy_window(screen_win);
	        screen_win = NULL;
	    }
	    eglTerminate(egl_disp);
	    egl_disp = EGL_NO_DISPLAY;
	}
	eglReleaseThread();
}

void
bbutil_swap() {
    int rc = eglSwapBuffers(egl_disp, egl_surf);
    if (rc != EGL_TRUE) {
        bbutil_egl_perror("eglSwapBuffers");
    }
}

void
bbutil_clear() {
    glClear(GL_COLOR_BUFFER_BIT);
}

char *
get_window_group_id()
{
	static char s_window_group_id[16] = "";

	if (s_window_group_id[0] == '\0') {
		snprintf(s_window_group_id, sizeof(s_window_group_id), "%d", getpid());
	}

	return s_window_group_id;
}


int bbutil_rotate_screen_surface(int angle) {
	int rc, rotation, skip = 1, temp;
	EGLint interval = 1;
	int size[2];

	if ((angle != 0) && (angle != 90) && (angle != 180) && (angle != 270)) {
		fprintf(stderr, "Invalid angle\n");
		return EXIT_FAILURE;
	}

	rc = screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_ROTATION, &rotation);
	if (rc) {
		perror("screen_set_window_property_iv");
		return EXIT_FAILURE;
	}

	rc = screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, size);
	if (rc) {
		perror("screen_set_window_property_iv");
		return EXIT_FAILURE;
	}

	switch (angle - rotation) {
		case -270:
		case -90:
		case 90:
		case 270:
			temp = size[0];
			size[0] = size[1];
			size[1] = temp;
			skip = 0;
			break;
	}

	if (!skip) {
		rc = eglMakeCurrent(egl_disp, NULL, NULL, NULL);
		if (rc != EGL_TRUE) {
			bbutil_egl_perror("eglMakeCurrent");
			return EXIT_FAILURE;
		}

		rc = eglDestroySurface(egl_disp, egl_surf);
		if (rc != EGL_TRUE) {
			bbutil_egl_perror("eglMakeCurrent");
			return EXIT_FAILURE;
		}

		rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_SOURCE_SIZE, size);
		if (rc) {
			perror("screen_set_window_property_iv");
			return EXIT_FAILURE;
		}

		rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, size);
		if (rc) {
			perror("screen_set_window_property_iv");
			return EXIT_FAILURE;
		}
		egl_surf = eglCreateWindowSurface(egl_disp, egl_conf, screen_win, NULL);
		if (egl_surf == EGL_NO_SURFACE) {
			bbutil_egl_perror("eglCreateWindowSurface");
			return EXIT_FAILURE;
		}

		rc = eglMakeCurrent(egl_disp, egl_surf, egl_surf, egl_ctx);
		if (rc != EGL_TRUE) {
			bbutil_egl_perror("eglMakeCurrent");
			return EXIT_FAILURE;
		}

		rc = eglSwapInterval(egl_disp, interval);
		if (rc != EGL_TRUE) {
			bbutil_egl_perror("eglSwapInterval");
			return EXIT_FAILURE;
		}
	}

	rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_ROTATION, &angle);
	if (rc) {
		perror("screen_set_window_property_iv");
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}