/// Copyright (c) 2012 Sundog Software LLC. All rights reserved worldwide.

#include <jni.h>
#include <errno.h>

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

#include <android/log.h>
#include <android_native_app_glue.h>

#include "SilverLining.h"
#include "AndroidResourceLoader.h"

using namespace SilverLining;
// Simulated visibility in meters, for fog effects.
#define kVisibility  20000.0f

static Atmosphere *atm = 0;

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "silverlining-native-activity", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "silverlining-native-activity", __VA_ARGS__))


// Configure high cirrus clouds.
static void SetupCirrusClouds()
{
    CloudLayer *cirrusCloudLayer;

    cirrusCloudLayer = CloudLayerFactory::Create(CIRRUS_FIBRATUS);
    cirrusCloudLayer->SetBaseAltitude(8000);
    cirrusCloudLayer->SetThickness(0);
    cirrusCloudLayer->SetBaseLength(100000);
    cirrusCloudLayer->SetBaseWidth(100000);
    cirrusCloudLayer->SetLayerPosition(0, 0);
    cirrusCloudLayer->SeedClouds(*atm);

    atm->GetConditions()->AddCloudLayer(cirrusCloudLayer);
}

// Add a cumulus congestus deck with 40% sky coverage.
static void SetupCumulusCongestusClouds()
{
    CloudLayer *cumulusCongestusLayer;

    cumulusCongestusLayer = CloudLayerFactory::Create(CUMULUS_CONGESTUS);
    cumulusCongestusLayer->SetIsInfinite(true);
    cumulusCongestusLayer->SetBaseAltitude(1500);
    cumulusCongestusLayer->SetThickness(100);
    cumulusCongestusLayer->SetBaseLength(30000);
    cumulusCongestusLayer->SetBaseWidth(30000);
    cumulusCongestusLayer->SetDensity(0.4);
    cumulusCongestusLayer->SetLayerPosition(0, 0);
    cumulusCongestusLayer->SetCloudAnimationEffects(0.1, false);
    cumulusCongestusLayer->SeedClouds(*atm);
    cumulusCongestusLayer->GenerateShadowMaps(false);

    atm->GetConditions()->AddCloudLayer(cumulusCongestusLayer);
}

// Sets up a solid stratus deck.
static void SetupStratusClouds()
{
    CloudLayer *stratusLayer;

    stratusLayer = CloudLayerFactory::Create(STRATUS);
    stratusLayer->SetIsInfinite(true);
    stratusLayer->SetBaseAltitude(1000);
    stratusLayer->SetThickness(600);
    stratusLayer->SetDensity(0.5);
    stratusLayer->SetLayerPosition(0, 0);
    stratusLayer->SeedClouds(*atm);

    atm->GetConditions()->AddCloudLayer(stratusLayer);
}

// A thunderhead; note a Cumulonimbus cloud layer contains a single cloud.
static void SetupCumulonimbusClouds()
{
    CloudLayer *cumulonimbusLayer;

    cumulonimbusLayer = CloudLayerFactory::Create(CUMULONIMBUS_CAPPILATUS);
    cumulonimbusLayer->SetBaseAltitude(1000);
    cumulonimbusLayer->SetThickness(3000);
    cumulonimbusLayer->SetBaseLength(3000);
    cumulonimbusLayer->SetBaseWidth(5000);
    cumulonimbusLayer->SetLayerPosition(5000, 5000);
    cumulonimbusLayer->SeedClouds(*atm);

    atm->GetConditions()->AddCloudLayer(cumulonimbusLayer);
}

// Cumulus mediocris are little, puffy clouds. Keep the density low for realism, otherwise
// you'll have a LOT of clouds because they are small.
static void SetupCumulusMediocrisClouds()
{
    CloudLayer *cumulusMediocrisLayer;

    cumulusMediocrisLayer = CloudLayerFactory::Create(CUMULUS_MEDIOCRIS);
    cumulusMediocrisLayer->SetIsInfinite(true);
    cumulusMediocrisLayer->SetBaseAltitude(1000);
    cumulusMediocrisLayer->SetThickness(100);
    cumulusMediocrisLayer->SetBaseLength(20000);
    cumulusMediocrisLayer->SetBaseWidth(20000);
    cumulusMediocrisLayer->SetDensity(0.2);
    cumulusMediocrisLayer->SetLayerPosition(0, 0);
    cumulusMediocrisLayer->SeedClouds(*atm);
    cumulusMediocrisLayer->GenerateShadowMaps(false);

    atm->GetConditions()->AddCloudLayer(cumulusMediocrisLayer);
}

// Configure SilverLining for the desired wind, clouds, and visibility.
static void SetupAtmosphericConditions()
{
    // Set up wind blowing south at 10 meters/sec
    WindVolume wv;
    wv.SetDirection(180);
    wv.SetMinAltitude(0);
    wv.SetMaxAltitude(10000);
    wv.SetWindSpeed(10);
    atm->GetConditions()->SetWind(wv);

    // Set up the desired cloud types.
    SetupCirrusClouds();
    SetupCumulusCongestusClouds();
    //SetupStratusClouds();
    //SetupCumulonimbusClouds();
    //SetupCumulusMediocrisClouds();

    // Set visibility
    atm->GetConditions()->SetVisibility(kVisibility);
}

// Sets the simulated location and local time.
// Note, it's important that your longitude in the Location agrees with
// the time zone in the LocalTime.
void SetTimeAndLocation()
{
    Location loc;
    loc.SetLatitude(45);
    loc.SetLongitude(-122);

    LocalTime tm;
    tm.SetYear(1971);
    tm.SetMonth(8);
    tm.SetDay(7);
    tm.SetHour(30);
    tm.SetMinutes(30);
    tm.SetSeconds(0);
    tm.SetObservingDaylightSavingsTime(true);
    tm.SetTimeZone(PST);

    atm->GetConditions()->SetTime(tm);
    atm->GetConditions()->SetLocation(loc);
}

/**
 * Shared state for our app.
 */
struct engine {
    struct android_app* app;

    int animating;
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    int32_t width;
    int32_t height;
};

/**
 * Initialize an EGL context for the current display.
 */
static int engine_init_display(struct engine* engine) {
    // initialize OpenGL ES and EGL

    /*
     * Here specify the attributes of the desired configuration.
     * Below, we select an EGLConfig with at least 8 bits per color
     * component compatible with on-screen windows
     */
    const EGLint attribs[] = {
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_BLUE_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_RED_SIZE, 8,
            EGL_DEPTH_SIZE, 16,
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
            EGL_NONE
    };
    EGLint w, h, dummy, format;
    EGLint numConfigs;
    EGLConfig config;
    EGLSurface surface;
    EGLContext context;

    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    eglInitialize(display, 0, 0);

    /* Here, the application chooses the configuration it desires. In this
     * sample, we have a very simplified selection process, where we pick
     * the first EGLConfig that matches our criteria */
    eglChooseConfig(display, attribs, &config, 1, &numConfigs);

    /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
     * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
     * As soon as we picked a EGLConfig, we can safely reconfigure the
     * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
    eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);

    ANativeWindow_setBuffersGeometry(engine->app->window, 0, 0, format);

    surface = eglCreateWindowSurface(display, config, engine->app->window, NULL);

    const EGLint attrib_list [] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
    context = eglCreateContext(display, config, NULL, attrib_list);

    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
        LOGW("Unable to eglMakeCurrent");
        return -1;
    }

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

    engine->display = display;
    engine->context = context;
    engine->surface = surface;
    engine->width = w;
    engine->height = h;

    // Initialize GL state.
    glEnable(GL_CULL_FACE);

    // Initialize SilverLining. Note how we provide an AndroidResourceLoader so SilverLining can
    // load its assets via our asset manager. Remember to include the assets/Resources directory
    // in your own app from our SDK's samples, or initialization will fail.
    
    atm = SL_NEW Atmosphere("user name", "license code");
    AndroidResourceLoader *rl = SL_NEW AndroidResourceLoader(engine->app->activity->assetManager);
    atm->SetResourceLoader(rl);
    int errcode = atm->Initialize(Atmosphere::OPENGLES2, "Resources", true, 0);
    if (errcode != Atmosphere::E_NOERROR) {
        LOGW("Did not intialize atmosphere. Error %d", errcode);
    }
 
    SetTimeAndLocation();
    SetupAtmosphericConditions();

    return 0;
}

static void frustum(double *matrix, double left, double right, double bottom, double top,
                  double znear, double zfar)
{
    double temp, temp2, temp3, temp4;
    temp = 2.0 * znear;
    temp2 = right - left;
    temp3 = top - bottom;
    temp4 = zfar - znear;
    matrix[0] = temp / temp2;
    matrix[1] = 0.0;
    matrix[2] = 0.0;
    matrix[3] = 0.0;
    matrix[4] = 0.0;
    matrix[5] = temp / temp3;
    matrix[6] = 0.0;
    matrix[7] = 0.0;
    matrix[8] = (right + left) / temp2;
    matrix[9] = (top + bottom) / temp3;
    matrix[10] = (-zfar - znear) / temp4;
    matrix[11] = -1.0;
    matrix[12] = 0.0;
    matrix[13] = 0.0;
    matrix[14] = (-temp * zfar) / temp4;
    matrix[15] = 0.0;
}

static void perspectiveMatrix(double *matrix, double fovyInDegrees, double aspectRatio,
                      double znear, double zfar)
{
    double ymax, xmax;
    double temp, temp2, temp3, temp4;
    ymax = znear * tanf(fovyInDegrees * M_PI / 360.0);
    xmax = ymax * aspectRatio;
    frustum(matrix, -xmax, xmax, -ymax, ymax, znear, zfar);
}

static void rotateXAxis(double *m, double degrees)
{
    double a = degrees * M_PI / 180.0;

    m[0] = 1.0;    m[1] = 0.0;      m[2] = 0.0;         m[3] = 0.0;
    m[4] = 0.0;    m[5] = cos(a);   m[6] = -sin(a);     m[7] = 0.0;
    m[8] = 0.0;    m[9] = sin(a);   m[10] = cos(a);     m[11] = 0.0;
    m[12] = 0.0;   m[13] = 0.0;     m[14] = 0.0;        m[15] = 1.0;
}

/**
 * Just the current frame in the display.
 */
static void engine_draw_frame(struct engine* engine) {
    if (engine->display == NULL) {
        // No display.
        return;
    }

    glViewport(0, 0, engine->width, engine->height);

    // Set the projection and modelview matrices and pass them into SilverLining:
    double projection[16];
    perspectiveMatrix(projection, 30.0, (double)engine->width / (double)engine->height, 10.0, 100000.0);
    double modelview[16];
    rotateXAxis(modelview, 10.0);
    atm->SetCameraMatrix(modelview);
    atm->SetProjectionMatrix(projection);
    
    // Draw the sky:
    atm->DrawSky(true);

    // You'd draw your own objects here - the sky should be drawn first, and the clouds last as they
    // are translucent.

    // Draw the clouds, weather effects, and other effects:
    atm->DrawObjects(true);

    eglSwapBuffers(engine->display, engine->surface);
}

/**
 * Tear down the EGL context currently associated with the display.
 */
static void engine_term_display(struct engine* engine) {
    if (engine->display != EGL_NO_DISPLAY) {
        if (atm) {
            SL_DELETE atm;
            atm = 0;
        }
        eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        if (engine->context != EGL_NO_CONTEXT) {
            eglDestroyContext(engine->display, engine->context);
        }
        if (engine->surface != EGL_NO_SURFACE) {
            eglDestroySurface(engine->display, engine->surface);
        }
        eglTerminate(engine->display);
    }
    engine->animating = 0;
    engine->display = EGL_NO_DISPLAY;
    engine->context = EGL_NO_CONTEXT;
    engine->surface = EGL_NO_SURFACE;
}

/**
 * Process the next input event.
 */
static int32_t engine_handle_input(struct android_app* app, AInputEvent* event) {
    struct engine* engine = (struct engine*)app->userData;
    engine->animating = 1;
    return 0;
}

/**
 * Process the next main command.
 */
static void engine_handle_cmd(struct android_app* app, int32_t cmd) {
    struct engine* engine = (struct engine*)app->userData;
    switch (cmd) {
        case APP_CMD_INIT_WINDOW:
            // The window is being shown, get it ready.
            if (engine->app->window != NULL) {
                engine_init_display(engine);
                engine_draw_frame(engine);
            }
            break;
        case APP_CMD_TERM_WINDOW:
            // The window is being hidden or closed, clean it up.
            engine_term_display(engine);
            break;

        case APP_CMD_LOST_FOCUS:
            engine->animating = 0;
            engine_draw_frame(engine);
            break;
    }
}

/**
 * This is the main entry point of a native application that is using
 * android_native_app_glue.  It runs in its own thread, with its own
 * event loop for receiving input events and doing other things.
 */
void android_main(struct android_app* state) {
    struct engine engine;

    // Make sure glue isn't stripped.
    app_dummy();

    memset(&engine, 0, sizeof(engine));
    state->userData = &engine;
    state->onAppCmd = engine_handle_cmd;
    state->onInputEvent = engine_handle_input;
    engine.app = state;
    engine.animating = 1;

    // loop waiting for stuff to do.

    while (1) {
        // Read all pending events.
        int ident;
        int events;
        struct android_poll_source* source;

        // If not animating, we will block forever waiting for events.
        // If animating, we loop until all events are read, then continue
        // to draw the next frame of animation.
        while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, NULL, &events,
                (void**)&source)) >= 0) {

            // Process this event.
            if (source != NULL) {
                source->process(state, source);
            }

            // Check if we are exiting.
            if (state->destroyRequested != 0) {
                engine_term_display(&engine);
                return;
            }
        }

        if (engine.animating) {
            // Done with events; draw next animation frame.
            // Drawing is throttled to the screen update rate, so there
            // is no need to do timing here.
            engine_draw_frame(&engine);
        }
    }
}
