// Filename: p3dSplashWindow.cxx
// Created by:  drose (17Jun09)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "p3dSplashWindow.h"
#include "wstring_encode.h"

// Stuff to use libpng.
#include <png.h>

// Stuff to use libjpeg.
extern "C" {
#include <jpeglib.h>
#include <jerror.h>
}

#include <setjmp.h>

struct my_error_mgr {
  struct jpeg_error_mgr pub;
  jmp_buf setjmp_buffer;
};

typedef struct my_error_mgr *my_error_ptr;

METHODDEF(void) my_error_exit (j_common_ptr cinfo) {
  // cinfo->err really points to a my_error_mgr struct, so coerce pointer
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  // Return control to the setjmp point
  longjmp(myerr->setjmp_buffer, 1);
}

// The number of pixels to move the block per byte downloaded, when we
// don't know the actual file size we're downloading.
const double P3DSplashWindow::_unknown_progress_rate = 1.0 / 4096;

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::Constructor
//       Access: Public
//  Description: By the time the SplashWindow is created, the instance
//               has received both its fparams and its wparams.  Copy
//               them both into this class for reference.
////////////////////////////////////////////////////////////////////
P3DSplashWindow::
P3DSplashWindow(P3DInstance *inst, bool make_visible) : 
  _inst(inst),
  _fparams(inst->get_fparams()),
  _wparams(inst->get_wparams())
{
  _visible = make_visible;
  _fgcolor_r = 0x00;
  _fgcolor_g = 0x00;
  _fgcolor_b = 0x00;
  _bgcolor_r = 0xff;
  _bgcolor_g = 0xff;
  _bgcolor_b = 0xff;
  _barcolor_r = 0x6c;
  _barcolor_g = 0xa5;
  _barcolor_b = 0xe0;
  _button_width = 0;
  _button_height = 0;
  _button_x = 0;
  _button_y = 0;
  _button_active = false;
  _mouse_x = -1;
  _mouse_y = -1;
  _mouse_down = false;
  _button_depressed = false;
  _bstate = BS_hidden;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::Destructor
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
P3DSplashWindow::
~P3DSplashWindow() {
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_wparams
//       Access: Public, Virtual
//  Description: Changes the window parameters, e.g. to resize or
//               reposition the window; or sets the parameters for the
//               first time, creating the initial window.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_wparams(const P3DWindowParams &wparams) {
  _wparams = wparams;
  _win_width = _wparams.get_win_width();
  _win_height = _wparams.get_win_height();
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_visible
//       Access: Public, Virtual
//  Description: Makes the splash window visible or invisible, so as
//               not to compete with the embedded Panda window in the
//               same space.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_visible(bool visible) {
  nout << "P3DSplashWindow::set_visible(" << visible << ")\n";
  _visible = visible;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_image_filename
//       Access: Public, Virtual
//  Description: Specifies the name of a JPEG or PNG image file that
//               is displayed in the center of the splash window.
//
//               image_placement defines the specific context in which
//               this particular image is displayed.  It is similar to
//               the P3DInstance's image_type, but it is a more
//               specific, lower-level usage.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_image_filename(const string &image_filename, ImagePlacement image_placement) {
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_fgcolor
//       Access: Public, Virtual
//  Description: Specifies the color that is used to display the text
//               above the loading bar.
//
//               This may only be set before wparams is set.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_fgcolor(int r, int g, int b) {
  nout << "fgcolor " << r << ", " << g << ", " << b << "\n";
  _fgcolor_r = r;
  _fgcolor_g = g;
  _fgcolor_b = b;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_bgcolor
//       Access: Public, Virtual
//  Description: Specifies the solid color that is displayed behind
//               the splash image, if any, or before the splash image
//               is loaded.
//
//               This may only be set before wparams is set.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_bgcolor(int r, int g, int b) {
  nout << "bgcolor " << r << ", " << g << ", " << b << "\n";
  _bgcolor_r = r;
  _bgcolor_g = g;
  _bgcolor_b = b;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_barcolor
//       Access: Public, Virtual
//  Description: Specifies the color that is used to fill the
//               loading bar.
//
//               This may only be set before wparams is set.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_barcolor(int r, int g, int b) {
  nout << "barcolor " << r << ", " << g << ", " << b << "\n";
  _barcolor_r = r;
  _barcolor_g = g;
  _barcolor_b = b;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_install_label
//       Access: Public, Virtual
//  Description: Specifies the text that is displayed above the
//               install progress bar.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_install_label(const string &install_label) {
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_install_progress
//       Access: Public, Virtual
//  Description: Moves the install progress bar from 0.0 to 1.0.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_install_progress(double install_progress,
                     bool is_progress_known, size_t received_data) {
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::handle_event
//       Access: Public, Virtual
//  Description: Deals with the event callback from the OS window
//               system.  Returns true if the event is handled, false
//               if ignored.
////////////////////////////////////////////////////////////////////
bool P3DSplashWindow::
handle_event(const P3D_event_data &event) {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_button_active
//       Access: Public, Virtual
//  Description: Sets whether the button should be visible and active
//               (true) or invisible and inactive (false).  If active,
//               the button image will be displayed in the window, and
//               a click event will be generated when the user clicks
//               the button.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_button_active(bool flag) {
  _button_active = flag;

  // Now light up the button according to the current mouse position.
  set_mouse_data(_mouse_x, _mouse_y, _mouse_down);
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::request_keyboard_focus
//       Access: Private
//  Description: The Panda window is asking us to manage keyboard
//               focus in proxy for it.  This is used on Vista, where
//               the Panda window may be disallowed from directly
//               assigning itself keyboard focus.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
request_keyboard_focus() {
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::read_image_data
//       Access: Protected
//  Description: Reads the image filename and sets image parameters
//               width, height, num_channels, and data.  Returns true
//               on success, false on failure.
////////////////////////////////////////////////////////////////////
bool P3DSplashWindow::
read_image_data(ImageData &image, string &data,
                const string &image_filename) {
  image._width = 0;
  image._height = 0;
  image._num_channels = 0;
  data.clear();

  if (image_filename.empty()) {
    return false;
  }

  // We only support JPEG or PNG images.
  FILE *fp = NULL;
#ifdef _WIN32
  wstring image_filename_w;
  if (string_to_wstring(image_filename_w, image_filename)) {
    fp = _wfopen(image_filename_w.c_str(), L"rb");
  }
#else // _WIN32
  fp = fopen(image_filename.c_str(), "rb");
#endif  // _WIN32

  if (fp == NULL) {
    nout << "Couldn't open splash file image: " << image_filename << "\n";
    return false;
  }

  // Check the magic number to determine which image type we have.
  static const size_t magic_number_len = 2;
  char magic_number[magic_number_len];
  if (fread(magic_number, 1, magic_number_len, fp) != magic_number_len) {
    nout << "Empty file: " << image_filename << "\n";
    return false;
  }
  // Rewind to re-read the magic number below.
  fseek(fp, 0, SEEK_SET);

  bool result = false;
  if ((char)magic_number[0] == (char)0xff &&
      (char)magic_number[1] == (char)0xd8) {
    // It's a jpeg image.
    result = read_image_data_jpeg(image, data, fp, image_filename);

  } else if (png_sig_cmp((png_bytep)magic_number, 0, magic_number_len) == 0) {
    // It's a PNG image.
    result = read_image_data_png(image, data, fp, image_filename);

  } else {
    nout << "Neither a JPEG nor a PNG image: " << image_filename << "\n";
    result = false;
  }

  fclose(fp);
  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::read_image_data_jpeg
//       Access: Protected
//  Description: Reads the image filename and sets image parameters
//               width, height, num_channels, and data.  Returns true
//               on success, false on failure.
////////////////////////////////////////////////////////////////////
bool P3DSplashWindow::
read_image_data_jpeg(ImageData &image, string &data,
                     FILE *fp, const string &image_filename) {
  // We set up the normal JPEG error routines, then override error_exit.
  struct jpeg_decompress_struct cinfo;

  struct my_error_mgr jerr;
  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;

  JSAMPLE *buffer = NULL;
  
  // Establish the setjmp return context for my_error_exit to use
  if (setjmp(jerr.setjmp_buffer)) {
    // If we get here, the JPEG code has signaled an error.
    nout << "JPEG error decoding " << image_filename << "\n";

    // We need to clean up the JPEG object, close the input file, and return.
    jpeg_destroy_decompress(&cinfo);

    if (buffer != NULL) {
      delete[] buffer;
    }
    return false;
  }

  /* Now we can initialize the JPEG decompression object. */
  jpeg_create_decompress(&cinfo);
  jpeg_stdio_src(&cinfo, fp);

  jpeg_read_header(&cinfo, true);

  cinfo.scale_num = 1;
  cinfo.scale_denom = 1;

  jpeg_start_decompress(&cinfo);

  image._width = cinfo.output_width;
  image._height = cinfo.output_height;
  image._num_channels = cinfo.output_components;

  int row_stride = image._width * image._num_channels;

  size_t buffer_size = image._height * row_stride;
  buffer = new JSAMPLE[buffer_size];
  JSAMPLE *buffer_end = buffer + buffer_size;

  JSAMPLE *rowptr = buffer;
  while (cinfo.output_scanline < cinfo.output_height) {
    assert(rowptr + row_stride <= buffer_end);
    jpeg_read_scanlines(&cinfo, &rowptr, 1);
    rowptr += row_stride;
  }

  jpeg_finish_decompress(&cinfo);

  data.append((const char *)buffer, buffer_size);
  delete[] buffer;

  return true;
}


////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::read_image_data_png
//       Access: Protected
//  Description: Reads the image filename and sets image parameters
//               width, height, num_channels, and data.  Returns true
//               on success, false on failure.
////////////////////////////////////////////////////////////////////
bool P3DSplashWindow::
read_image_data_png(ImageData &image, string &data,
                    FILE *fp, const string &image_filename) {
  png_structp png;
  png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
                                png_error, png_warning);
  if (png == NULL) {
    return false;
  }

  png_infop info;
  info = png_create_info_struct(png);
  if (info == NULL) {
    png_destroy_read_struct(&png, NULL, NULL);
    return false;
  }

  jmp_buf jmpbuf;
  if (setjmp(jmpbuf)) {
    // This is the ANSI C way to handle exceptions.  If setjmp(),
    // above, returns true, it means that libpng detected an exception
    // while executing the code that reads the header info, below.
    png_destroy_read_struct(&png, &info, NULL);
    return false;
  }

  png_init_io(png, fp);
  int transforms = PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_SHIFT;
  //  transforms |= PNG_TRANSFORM_STRIP_ALPHA;
  png_read_png(png, info, transforms, NULL);

  png_uint_32 width;
  png_uint_32 height;
  int bit_depth;
  int color_type;

  png_get_IHDR(png, info, &width, &height,
               &bit_depth, &color_type, NULL, NULL, NULL);

  image._width = width;
  image._height = height;

  switch (color_type) {
  case PNG_COLOR_TYPE_RGB:
    image._num_channels = 3;
    break;

  case PNG_COLOR_TYPE_RGB_ALPHA:
    image._num_channels = 4;
    break;

  default:
    nout << "Unsupported color type: " << color_type << "\n";
    png_destroy_read_struct(&png, &info, NULL);
    return false;
  }

  int row_stride = image._width * image._num_channels;
  png_bytep *row_pointers = png_get_rows(png, info);
  for (int yi = 0; yi < image._height; ++yi) {
    data.append((const char *)row_pointers[yi], row_stride);
  }

  png_destroy_read_struct(&png, &info, NULL);
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::get_bar_placement
//       Access: Protected
//  Description: Given the window width and height, determine the
//               rectangle in which to place the progress bar.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
get_bar_placement(int &bar_x, int &bar_y,
                  int &bar_width, int &bar_height) {
  bar_width = min((int)(_win_width * 0.6), 400);
  bar_height = min((int)(_win_height * 0.1), 24);
  bar_x = (_win_width - bar_width) / 2;
  bar_y = (_win_height - bar_height * 2);
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_button_range
//       Access: Protected
//  Description: Specifies the image that contains the "ready" button
//               image, which in turn determines the clickable
//               dimensions of the button within the window.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_button_range(const ImageData &image) {
  // The clickable area has a certain minimum size, even if it's a
  // very small image.
  _button_width = max(image._width, 64);
  _button_height = max(image._height, 64);

  // But it can't be larger than the window itself.
  _button_width = min(_button_width, _win_width);
  _button_height = min(_button_height, _win_height);
      
  // Compute the top-left corner of the button image in window
  // coordinates.
  _button_x = (_win_width - _button_width) / 2;
  _button_y = (_win_height - _button_height) / 2;
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_mouse_data
//       Access: Protected
//  Description: Intended to be called by the subclasses as the mouse
//               is tracked through the window, whether the button is
//               currently active or not.  This updates the internal
//               state of the mouse pointer, and also (if the button
//               is active) updates the button state appropriately,
//               and generates the click event when the mouse button
//               transitions from down to up over the button area.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_mouse_data(int mouse_x, int mouse_y, bool mouse_down) {
  ButtonState orig_bstate = _bstate;

  _mouse_x = mouse_x;
  _mouse_y = mouse_y;
  _mouse_down = mouse_down;

  ButtonState bstate = BS_hidden;
  bool click_detected = false;

  if (!_button_active) {
    // The button isn't active, so it's hidden, regardless of the
    // mouse position.
    bstate = BS_hidden;
  } else {
    // Is the mouse pointer within the button region?
    bool is_within = (_mouse_x >= _button_x && _mouse_x < _button_x + _button_width &&
                      _mouse_y >= _button_y && _mouse_y < _button_y + _button_height);
    if (is_within) {
      // The mouse is within the button region.  This means either
      // click or rollover state, according to the mouse button.
      if (_mouse_down) {
        // We only count it mouse-down if you've clicked down while
        // over the button (or you never released the button since the
        // last time you clicked down).  Clicking down somewhere else
        // and dragging over the button doesn't count.
        if (orig_bstate == BS_rollover || _button_depressed) {
          _button_depressed = true;
          bstate = BS_click;
        } else {
          // Otherwise, we're mousing over the button region with the
          // button held down.  Hmm, don't think the button should
          // light up.
          bstate = BS_ready;
        }
      } else {
        _button_depressed = false;
        if (orig_bstate == BS_click) {
          // If we just transitioned from mouse down to mouse up, this
          // means a click.  And the button automatically hides itself
          // after a successful click.
          bstate = BS_hidden;
          _button_active = false;
          click_detected = true;
        } else {
          bstate = BS_rollover;
        }
      }
    } else {
      // The mouse is not within the button region.  This means ready
      // state.
      bstate = BS_ready;
      if (!_mouse_down) {
        _button_depressed = false;
      }
    }
  }

  set_bstate(bstate);

  // If we detected a click operation in the above, make the callback
  // here, at the end of the method, after we have finished updating
  // the button state.
  if (click_detected) {
    button_click_detected();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::button_click_detected
//       Access: Protected, Virtual
//  Description: Called when a button click by the user is detected in
//               set_mouse_data(), this method simply turns around and
//               notifies the instance.  It's a virtual method to give
//               subclasses a chance to redirect this message to the
//               main thread or process, as necessary.
//
//               Note that this method might be called in a sub-thread.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
button_click_detected() {
  assert(_inst != NULL);
  nout << "Play button clicked by user\n";
  _inst->splash_button_clicked_sub_thread();
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::set_bstate
//       Access: Protected, Virtual
//  Description: Changes the button state as the mouse interacts with
//               it.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
set_bstate(ButtonState bstate) {
  if (_bstate != bstate) {
    _bstate = bstate;
    // If we've changed button states, we need to refresh the window.
    refresh();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: P3DSplashWindow::refresh
//       Access: Protected, Virtual
//  Description: Requests that the window will be repainted.  This may
//               or may not be implemented for a particular
//               specialization of P3DSplashWindow.
////////////////////////////////////////////////////////////////////
void P3DSplashWindow::
refresh() {
}
