/*         ______   ___    ___
 *        /\  _  \ /\_ \  /\_ \
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      New display driver.
 *
 *      By Elias Pschernig.
 *
 *      Modified by Trent Gamblin.
 *
 *      See readme.txt for copyright information.
 */

/* Title: Display routines
 */



#include "allegro5/allegro.h"
#include "allegro5/internal/aintern.h"
#include "allegro5/internal/aintern_bitmap.h"
#include "allegro5/internal/aintern_display.h"
#include "allegro5/internal/aintern_shader.h"
#include "allegro5/internal/aintern_system.h"


ALLEGRO_DEBUG_CHANNEL("display")


/* Function: al_create_display
 */
ALLEGRO_DISPLAY *al_create_display(int w, int h)
{
   ALLEGRO_SYSTEM *system;
   ALLEGRO_DISPLAY_INTERFACE *driver;
   ALLEGRO_DISPLAY *display;
   ALLEGRO_EXTRA_DISPLAY_SETTINGS *settings;
   int64_t flags;

   system = al_get_system_driver();
   driver = system->vt->get_display_driver();
   if (!driver) {
      ALLEGRO_ERROR("Failed to create display (no display driver)\n");
      return NULL;
   }

   display = driver->create_display(w, h);
   if (!display) {
      ALLEGRO_ERROR("Failed to create display (NULL)\n");
      return NULL;
   }

   ASSERT(display->vt);

   settings = &display->extra_settings;
   flags = settings->required | settings->suggested;
   if (!(flags & (1 << ALLEGRO_AUTO_CONVERT_BITMAPS))) {
      settings->settings[ALLEGRO_AUTO_CONVERT_BITMAPS] = 1;
   }
   settings->settings[ALLEGRO_DEFAULT_SHADER_PLATFORM] =
      _al_get_new_display_settings()->settings[ALLEGRO_DEFAULT_SHADER_PLATFORM];

   display->min_w = 0;
   display->min_h = 0;
   display->max_w = 0;
   display->max_h = 0;
   display->use_constraints = false;

   display->vertex_cache = 0;
   display->num_cache_vertices = 0;
   display->cache_enabled = false;
   display->vertex_cache_size = 0;
   display->cache_texture = 0;
   al_identity_transform(&display->projview_transform);

   display->default_shader = NULL;

   _al_vector_init(&display->display_invalidated_callbacks, sizeof(void *));
   _al_vector_init(&display->display_validated_callbacks, sizeof(void *));

   display->render_state.write_mask = ALLEGRO_MASK_RGBA | ALLEGRO_MASK_DEPTH;
   display->render_state.depth_test = false;
   display->render_state.depth_function = ALLEGRO_RENDER_LESS;
   display->render_state.alpha_test = false;
   display->render_state.alpha_function = ALLEGRO_RENDER_ALWAYS;
   display->render_state.alpha_test_value = 0;

   _al_vector_init(&display->bitmaps, sizeof(ALLEGRO_BITMAP*));

   if (settings->settings[ALLEGRO_COMPATIBLE_DISPLAY]) {
      al_set_target_bitmap(al_get_backbuffer(display));
   }
   else {
      ALLEGRO_DEBUG("ALLEGRO_COMPATIBLE_DISPLAY not set\n");
      _al_set_current_display_only(display);
   }

   if (display->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
      display->default_shader = _al_create_default_shader(display);
      if (!display->default_shader) {
         al_destroy_display(display);
         return NULL;
      }
      al_use_shader(display->default_shader);
   }

   /* Clear the screen */
   if (settings->settings[ALLEGRO_COMPATIBLE_DISPLAY]) {
      al_clear_to_color(al_map_rgb(0, 0, 0));

      /* TODO:
       * on iphone, don't kill the initial splashscreen - in fact, it's also
       * annoying in linux to have an extra black frame as first frame and I
       * suppose we never really want it
       */
#if 0
      al_flip_display();
#endif
   }

   if (settings->settings[ALLEGRO_AUTO_CONVERT_BITMAPS]) {
      /* We convert video bitmaps to memory bitmaps when the display is
       * destroyed, so seems only fair to re-convertt hem when the
       * display is re-created again.
       */
      al_convert_memory_bitmaps();
   }

   return display;
}



/* Function: al_destroy_display
 */
void al_destroy_display(ALLEGRO_DISPLAY *display)
{
   if (display) {
      /* This causes warnings and potential errors on Android because
       * it clears the context and Android needs this thread to have
       * the context bound in its destroy function and to destroy the
       * shader. Just skip this part on Android.
       */
#ifndef ALLEGRO_ANDROID
      ALLEGRO_BITMAP *bmp;

      bmp = al_get_target_bitmap();
      if (bmp && _al_get_bitmap_display(bmp) == display)
         al_set_target_bitmap(NULL);

      /* This can happen if we have a current display, but the target bitmap
       * was a memory bitmap.
       */
      if (display == al_get_current_display())
         _al_set_current_display_only(NULL);
#endif

      al_destroy_shader(display->default_shader);
      display->default_shader = NULL;

      ASSERT(display->vt);
      display->vt->destroy_display(display);
   }
}



/* Function: al_get_backbuffer
 */
ALLEGRO_BITMAP *al_get_backbuffer(ALLEGRO_DISPLAY *display)
{
   if (display) {
      ASSERT(display->vt);
      return display->vt->get_backbuffer(display);
   }
   return NULL;
}



/* Function: al_flip_display
 */
void al_flip_display(void)
{
   ALLEGRO_DISPLAY *display = al_get_current_display();

   if (display) {
      ASSERT(display->vt);
      display->vt->flip_display(display);
   }
}



/* Function: al_update_display_region
 */
void al_update_display_region(int x, int y, int width, int height)
{
   ALLEGRO_DISPLAY *display = al_get_current_display();

   if (display) {
      ASSERT(display->vt);
      display->vt->update_display_region(display, x, y, width, height);
   }
}



/* Function: al_acknowledge_resize
 */
bool al_acknowledge_resize(ALLEGRO_DISPLAY *display)
{
   ASSERT(display);
   ASSERT(display->vt);

   if (!(display->flags & ALLEGRO_FULLSCREEN)) {
      if (display->vt->acknowledge_resize) {
         return display->vt->acknowledge_resize(display);
      }
   }
   return false;
}



/* Function: al_resize_display
 */
bool al_resize_display(ALLEGRO_DISPLAY *display, int width, int height)
{
   ASSERT(display);
   ASSERT(display->vt);

   ALLEGRO_INFO("Requested display resize %dx%d\n", width, height);

   if (display->vt->resize_display) {
      return display->vt->resize_display(display, width, height);
   }
   return false;
}



/* Function: al_is_compatible_bitmap
 */
bool al_is_compatible_bitmap(ALLEGRO_BITMAP *bitmap)
{
   ALLEGRO_DISPLAY *display = al_get_current_display();
   ASSERT(bitmap);

   if (display) {
      ASSERT(display->vt);
      return display->vt->is_compatible_bitmap(display, bitmap);
   }

   return false;
}



/* Function: al_get_display_width
 */
int al_get_display_width(ALLEGRO_DISPLAY *display)
{
   ASSERT(display);

   return display->w;
}



/* Function: al_get_display_height
 */
int al_get_display_height(ALLEGRO_DISPLAY *display)
{
   ASSERT(display);

   return display->h;
}


/* Function: al_get_display_format
 */
int al_get_display_format(ALLEGRO_DISPLAY *display)
{
   ASSERT(display);

   return display->backbuffer_format;
}


/* Function: al_get_display_refresh_rate
 */
int al_get_display_refresh_rate(ALLEGRO_DISPLAY *display)
{
   ASSERT(display);

   return display->refresh_rate;
}



/* Function: al_get_display_flags
 */
int al_get_display_flags(ALLEGRO_DISPLAY *display)
{
   ASSERT(display);

   return display->flags;
}


/* Function: al_get_display_orientation
 */
int al_get_display_orientation(ALLEGRO_DISPLAY* display)
{
   if (display && display->vt->get_orientation)
      return display->vt->get_orientation(display);
   else
      return ALLEGRO_DISPLAY_ORIENTATION_UNKNOWN;
}


/* Function: al_wait_for_vsync
 */
bool al_wait_for_vsync(void)
{
   ALLEGRO_DISPLAY *display = al_get_current_display();
   ASSERT(display);

   if (display->vt->wait_for_vsync)
      return display->vt->wait_for_vsync(display);
   else
      return false;
}



/* Function: al_set_display_icon
 */
void al_set_display_icon(ALLEGRO_DISPLAY *display, ALLEGRO_BITMAP *icon)
{
   ALLEGRO_BITMAP *icons[1] = { icon };

   al_set_display_icons(display, 1, icons);
}



/* Function: al_set_display_icons
 */
void al_set_display_icons(ALLEGRO_DISPLAY *display,
   int num_icons, ALLEGRO_BITMAP *icons[])
{
   int i;

   ASSERT(display);
   ASSERT(num_icons >= 1);
   ASSERT(icons);
   for (i = 0; i < num_icons; i++) {
      ASSERT(icons[i]);
   }

   if (display->vt->set_icons) {
      display->vt->set_icons(display, num_icons, icons);
   }
}

/* Function: al_set_window_position
 */
void al_set_window_position(ALLEGRO_DISPLAY *display, int x, int y)
{
   ASSERT(display);

   if (display && display->flags & ALLEGRO_FULLSCREEN) {
      return;
   }

   if (display && display->vt && display->vt->set_window_position) {
      display->vt->set_window_position(display, x, y);
   }
}


/* Function: al_get_window_position
 */
void al_get_window_position(ALLEGRO_DISPLAY *display, int *x, int *y)
{
   ASSERT(x);
   ASSERT(y);

   if (display && display->vt && display->vt->get_window_position) {
      display->vt->get_window_position(display, x, y);
   }
   else {
      *x = *y = -1;
   }
}


/* Function: al_get_window_borders
 */
bool al_get_window_borders(ALLEGRO_DISPLAY *display, int *left, int *top, int *right, int *bottom)
{
   if (display && display->vt && display->vt->get_window_borders) {
      return display->vt->get_window_borders(display, left, top, right, bottom);
   }
   else {
      return false;
   }
}


/* Function: al_set_window_constraints
 */
bool al_set_window_constraints(ALLEGRO_DISPLAY *display,
   int min_w, int min_h, int max_w, int max_h)
{
   ASSERT(display);

  /* Perform some basic checks. */
   if (min_w < 0 || min_h < 0 || max_w < 0 || max_h < 0) {
      return false;
   }
   if (min_w > 0 && max_w > 0 && max_w < min_w) {
      return false;
   }
   if (min_h > 0 && max_h > 0 && max_h < min_h) {
      return false;
   }

   /* Cannot constrain when fullscreen. */
   if (display->flags & ALLEGRO_FULLSCREEN) {
      return false;
   }

   /* Cannot constrain if not resizable. */
   if (!(display->flags & ALLEGRO_RESIZABLE)) {
      return false;
   }

   if (display && display->vt && display->vt->set_window_constraints) {
      return display->vt->set_window_constraints(display, min_w, min_h,
         max_w, max_h);
   }
   else {
      return false;
   }
}


/* Function: al_get_window_constraints
 */
bool al_get_window_constraints(ALLEGRO_DISPLAY *display,
   int *min_w, int *min_h, int *max_w, int *max_h)
{
   ASSERT(display);
   ASSERT(min_w);
   ASSERT(min_h);
   ASSERT(max_w);
   ASSERT(max_h);

   if (display && display->vt && display->vt->get_window_constraints) {
      return display->vt->get_window_constraints(display, min_w, min_h,
         max_w, max_h);
   }
   else {
      return false;
   }
}


/* Function: al_set_display_flag
 */
bool al_set_display_flag(ALLEGRO_DISPLAY *display, int flag, bool onoff)
{
   ASSERT(display);

   if (display && display->vt && display->vt->set_display_flag) {
      return display->vt->set_display_flag(display, flag, onoff);
   }
   return false;
}


/* Function: al_set_window_title
 */
void al_set_window_title(ALLEGRO_DISPLAY *display, const char *title)
{
   if (display && display->vt && display->vt->set_window_title)
      display->vt->set_window_title(display, title);
}


/* Function: al_get_display_event_source
 */
ALLEGRO_EVENT_SOURCE *al_get_display_event_source(ALLEGRO_DISPLAY *display)
{
   return &display->es;
}

/* Function: al_hold_bitmap_drawing
 */
void al_hold_bitmap_drawing(bool hold)
{
   ALLEGRO_DISPLAY *current_display = al_get_current_display();

   if (current_display) {
      if (hold && !current_display->cache_enabled) {
         /*
          * Set the hardware transformation to identity, but keep the bitmap
          * transform the same as it was. Relies on the fact that when bitmap
          * holding is turned on, al_use_transform does not update the hardware
          * transformation.
          */
         ALLEGRO_TRANSFORM old, ident;
         al_copy_transform(&old, al_get_current_transform());
         al_identity_transform(&ident);

         al_use_transform(&ident);
         current_display->cache_enabled = hold;
         al_use_transform(&old);
      }
      else {
         current_display->cache_enabled = hold;
      }

      if (!hold) {
         current_display->vt->flush_vertex_cache(current_display);
         /*
          * Reset the hardware transform to match the stored transform.
          */
         al_use_transform(al_get_current_transform());
      }
   }
}

/* Function: al_is_bitmap_drawing_held
 */
bool al_is_bitmap_drawing_held(void)
{
   ALLEGRO_DISPLAY *current_display = al_get_current_display();

   if (current_display)
      return current_display->cache_enabled;
   else
      return false;
}

void _al_add_display_invalidated_callback(ALLEGRO_DISPLAY* display, void (*display_invalidated)(ALLEGRO_DISPLAY*))
{
   if (_al_vector_find(&display->display_invalidated_callbacks, display_invalidated) >= 0) {
      return;
   }
   else {
      void (**callback)(ALLEGRO_DISPLAY *) = _al_vector_alloc_back(&display->display_invalidated_callbacks);
      *callback = display_invalidated;
   }
}

void _al_add_display_validated_callback(ALLEGRO_DISPLAY* display, void (*display_validated)(ALLEGRO_DISPLAY*))
{
   if (_al_vector_find(&display->display_validated_callbacks, display_validated) >= 0) {
      return;
   }
   else {
      void (**callback)(ALLEGRO_DISPLAY *) = _al_vector_alloc_back(&display->display_validated_callbacks);
      *callback = display_validated;
   }
}

void _al_remove_display_invalidated_callback(ALLEGRO_DISPLAY *display, void (*callback)(ALLEGRO_DISPLAY *))
{
   _al_vector_find_and_delete(&display->display_invalidated_callbacks, &callback);
}

void _al_remove_display_validated_callback(ALLEGRO_DISPLAY *display, void (*callback)(ALLEGRO_DISPLAY *))
{
   _al_vector_find_and_delete(&display->display_validated_callbacks, &callback);
}

/* Function: al_acknowledge_drawing_halt
 */
void al_acknowledge_drawing_halt(ALLEGRO_DISPLAY *display)
{
   if (display->vt->acknowledge_drawing_halt) {
      display->vt->acknowledge_drawing_halt(display);
   }
}

/* Function: al_acknowledge_drawing_resume
 */
void al_acknowledge_drawing_resume(ALLEGRO_DISPLAY *display)
{
   if (display->vt->acknowledge_drawing_resume) {
      display->vt->acknowledge_drawing_resume(display);
   }
}

/* Function: al_get_render_state
 */
int al_get_render_state(ALLEGRO_RENDER_STATE state)
{
   ALLEGRO_DISPLAY *display = al_get_current_display();

   if (!display)
      return -1;

   switch (state) {
      case ALLEGRO_ALPHA_TEST:
         return display->render_state.alpha_test;
         break;
      case ALLEGRO_WRITE_MASK:
         return display->render_state.write_mask;
         break;
      case ALLEGRO_DEPTH_TEST:
         return display->render_state.depth_test;
         break;
      case ALLEGRO_DEPTH_FUNCTION:
         return display->render_state.depth_function;
         break;
      case ALLEGRO_ALPHA_FUNCTION:
         return display->render_state.alpha_function;
         break;
      case ALLEGRO_ALPHA_TEST_VALUE:
         return display->render_state.alpha_test_value;
         break;
      default:
         ALLEGRO_ERROR("Unknown state to retrieve: %d\n", state);
         return -1;
         break;
   }
}

/* Function: al_set_render_state
 */
void al_set_render_state(ALLEGRO_RENDER_STATE state, int value)
{
   ALLEGRO_DISPLAY *display = al_get_current_display();

   if (!display)
      return;

   switch (state) {
      case ALLEGRO_ALPHA_TEST:
         display->render_state.alpha_test = value;
         break;
      case ALLEGRO_WRITE_MASK:
         display->render_state.write_mask = value;
         break;
      case ALLEGRO_DEPTH_TEST:
         display->render_state.depth_test = value;
         break;
      case ALLEGRO_DEPTH_FUNCTION:
         display->render_state.depth_function = value;
         break;
      case ALLEGRO_ALPHA_FUNCTION:
         display->render_state.alpha_function = value;
         break;
      case ALLEGRO_ALPHA_TEST_VALUE:
         display->render_state.alpha_test_value = value;
         break;
      default:
         ALLEGRO_WARN("Unknown state to change: %d\n", state);
         break;
   }

   if (display->vt && display->vt->update_render_state) {
      display->vt->update_render_state(display);
   }
}

/* Function: al_backup_dirty_bitmaps
 */
void al_backup_dirty_bitmaps(ALLEGRO_DISPLAY *display)
{
   unsigned int i;

   for (i = 0; i < display->bitmaps._size; i++) {
      ALLEGRO_BITMAP **bptr = (ALLEGRO_BITMAP **)_al_vector_ref(&display->bitmaps, i);
      ALLEGRO_BITMAP *bmp = *bptr;
      if (_al_get_bitmap_display(bmp) == display) {
         if (bmp->vt && bmp->vt->backup_dirty_bitmap) {
            bmp->vt->backup_dirty_bitmap(bmp);
	 }
      }
   }
}

/* Function: al_apply_window_constraints
 */
void al_apply_window_constraints(ALLEGRO_DISPLAY *display, bool onoff)
{
   display->use_constraints = onoff;

   if (display->vt && display->vt->apply_window_constraints)
      display->vt->apply_window_constraints(display, onoff);
}

/* Function: al_get_display_adapter
 */
int al_get_display_adapter(ALLEGRO_DISPLAY *display)
{
   int x, y, adapter, num_adapters;
   al_get_window_position(display, &x, &y);
   num_adapters = al_get_num_video_adapters();
   for (adapter = 0; adapter < num_adapters; adapter++) {
      ALLEGRO_MONITOR_INFO mi;
      al_get_monitor_info(adapter, &mi);
      if (x >= mi.x1 && x < mi.x2 && y >= mi.y1 && y < mi.y2) {
         return adapter;
      }
   }
   return -1;
}


/* vim: set sts=3 sw=3 et: */