// Filename: iPhoneGraphicsWindow.mm
// Created by:  drose (08Apr09)
//
////////////////////////////////////////////////////////////////////
//
// 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."
//
////////////////////////////////////////////////////////////////////

// We include these system header files first, because there is a
// namescope conflict between them and some other header file that
// gets included later (in particular, TCP_NODELAY must not be a
// #define symbol for these headers to be included properly).

#include <UIKit/UIKit.h>

#include "iPhoneGraphicsWindow.h"
#include "dcast.h"
#include "config_iphonedisplay.h"
#include "iPhoneGraphicsPipe.h"
#include "pStatTimer.h"
#include "glesgsg.h"
#include "keyboardButton.h"
#include "mouseButton.h"
#include "iPhoneGraphicsStateGuardian.h"
#include "iPhoneGraphicsPipe.h"
#include "throw_event.h"
#include "pnmImage.h"
#include "virtualFileSystem.h"
#include "config_util.h"
#include "pset.h"
#include "pmutex.h"

TypeHandle IPhoneGraphicsWindow::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
IPhoneGraphicsWindow::
IPhoneGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, 
                     const string &name,
                     const FrameBufferProperties &fb_prop,
                     const WindowProperties &win_prop,
                     int flags,
                     GraphicsStateGuardian *gsg,
                     GraphicsOutput *host) :
  GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
{
  IPhoneGraphicsPipe *ipipe;
  DCAST_INTO_V(ipipe, _pipe);
  ipipe->_graphics_windows.insert(this);
  _gl_view = nil;
  _last_buttons = 0;

  GraphicsWindowInputDevice device =
    GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard/mouse");
  _input_devices.push_back(device);
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::Destructor
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
IPhoneGraphicsWindow::
~IPhoneGraphicsWindow() {
  if (_gl_view != nil) {
    [ _gl_view release ];
  }
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::begin_frame
//       Access: Public, Virtual
//  Description: This function will be called within the draw thread
//               before beginning rendering for a given frame. It
//               should do whatever setup is required, and return true
//               if the frame should be rendered, or false if it
//               should be skipped.
////////////////////////////////////////////////////////////////////
bool IPhoneGraphicsWindow::
begin_frame(FrameMode mode, Thread *current_thread) {
  PStatTimer timer(_make_current_pcollector);
 
  begin_frame_spam(mode);
  if (_gsg == (GraphicsStateGuardian *)NULL) {
    // not powered up .. just abort..
   return false;
  }

  _gsg->reset_if_new();
  _gsg->set_current_properties(&get_fb_properties());

  return _gsg->begin_frame(current_thread);
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::end_frame
//       Access: Public, Virtual
//  Description: This function will be called within the draw thread
//               after rendering is completed for a given frame. It
//               should do whatever finalization is required.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
end_frame(FrameMode mode, Thread *current_thread) {
  end_frame_spam(mode);
 
  if (mode == FM_render) {
    nassertv(_gsg != (GraphicsStateGuardian *)NULL);

    copy_to_textures();
    
    _gsg->end_frame(current_thread);

    if (_gl_view != nil) {
      [_gl_view presentView];
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::begin_flip
//       Access: Public, Virtual
//  Description: This function will be called within the draw thread
//               after end_frame() has been called on all windows, to
//               initiate the exchange of the front and back buffers.
//
//               This should instruct the window to prepare for the
//               flip at the next video sync, but it should not wait.
//
//               We have the two separate functions, begin_flip() and
//               end_flip(), to make it easier to flip all of the
//               windows at the same time.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
end_flip() {
}

void IPhoneGraphicsWindow::
begin_flip() {
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::process_events
//       Access: Protected, Virtual
//  Description: Required event upcall, used to dispatch window and
//               application events back into panda.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
process_events() {
  GraphicsWindow::process_events();
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::set_properties_now
//       Access: Public, Virtual
//  Description: Applies the requested set of properties to the
//               window, if possible, for instance to request a change
//               in size or minimization status.
//
//               The window properties are applied immediately, rather
//               than waiting until the next frame. This implies that
//               this method may *only* be called from within the
//               window thread.
//
//               The properties that have been applied are cleared
//               from the structure by this function; so on return,
//               whatever remains in the properties structure are
//               those that were unchanged for some reason (probably
//               because the underlying interface does not support
//               changing that property on an open window).
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
set_properties_now(WindowProperties &properties) {
  if (iphonedisplay_cat.is_debug()) {
    iphonedisplay_cat.debug()
      << "------------------------------------------------------\n";
    iphonedisplay_cat.debug()
      << "set_properties_now " << properties << "\n";
  }
 
  GraphicsWindow::set_properties_now(properties);
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::clear_pipe
//       Access: Protected, Virtual
//  Description: Sets the window's _pipe pointer to NULL; this is
//               generally called only as a precursor to deleting the
//               window.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
clear_pipe() {
  IPhoneGraphicsPipe *ipipe;
  DCAST_INTO_V(ipipe, _pipe);
  ipipe->_graphics_windows.erase(this);

  GraphicsWindow::clear_pipe();
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::rotate_window
//       Access: Public
//  Description: Called in response to an orientation change event,
//               this tells the window to resize itself according to
//               the new orientation.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
rotate_window() {
  CGRect bounds = [_gl_view bounds];

  WindowProperties properties;
  properties.set_size(bounds.size.width, bounds.size.height);
  system_changed_properties(properties);
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::touches_began
//       Access: Public
//  Description: Beginning a single- or multi-touch gesture.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
touches_began(NSSet *touches, UIEvent *event) {
  // Average the position of all of the touches.
  CGPoint location = get_average_location(touches);

  set_pointer_in_window(location.x, location.y);
  handle_button_delta([touches count]);
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::touches_moved
//       Access: Public
//  Description: Continuing a single- or multi-touch gesture.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
touches_moved(NSSet *touches, UIEvent *event) {
  // Average the position of all of the touches.
  CGPoint location = get_average_location(touches);

  set_pointer_in_window(location.x, location.y);
  handle_button_delta([touches count]);
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::touches_ended
//       Access: Public
//  Description: Finishing a single- or multi-touch gesture.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
touches_ended(NSSet *touches, UIEvent *event) {
  set_pointer_out_of_window();
  handle_button_delta(0);
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::touches_cancelled
//       Access: Public
//  Description: Cancelling a single- or multi-touch gesture.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
touches_cancelled(NSSet *touches, UIEvent *event) {
  set_pointer_out_of_window();
  handle_button_delta(0);
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::get_average_location
//       Access: Public
//  Description: Returns the average location of all of the indicated
//               touches.
////////////////////////////////////////////////////////////////////
CGPoint IPhoneGraphicsWindow::
get_average_location(NSSet *touches) {
  NSEnumerator *enumerator = [ touches objectEnumerator ]; 
  CGPoint sum;
  sum.x = 0.0;
  sum.y = 0.0;

  UITouch *touch;
  while ((touch = [ enumerator nextObject ])) {
    CGPoint location = [ touch locationInView: _gl_view ];
    sum.x += location.x;
    sum.y += location.y;
  }

  int count = [touches count];
  sum.x /= count;
  sum.y /= count;
  return sum;
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::close_window
//       Access: Protected, Virtual
//  Description: Closes the window right now. Called from the window
//               thread.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
close_window() {
  //  system_close_window();

  WindowProperties properties;
  properties.set_open(false);
  system_changed_properties(properties);

//  release_system_resources(false);
  _gsg.clear();
  _active = false;
  GraphicsWindow::close_window();
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::open_window
//       Access: Protected, Virtual
//  Description: Opens the window right now. Called from the window
//               thread. Returns true if the window is successfully
//               opened, or false if there was a problem.
////////////////////////////////////////////////////////////////////
bool IPhoneGraphicsWindow::
open_window() {
  nassertr(_gsg == (GraphicsStateGuardian *)NULL, false);

  _gl_view = [ [ EAGLView alloc ] initWithFrame: 
        [ [ UIScreen mainScreen ] applicationFrame ] 
    ]; 
  _gl_view->_window = this;
  
  IPhoneGraphicsPipe *iphonepipe = DCAST(IPhoneGraphicsPipe, _pipe);
  nassertr(iphonepipe != NULL, false);

  iphonepipe->_view_controller.view = _gl_view;
  [ _gl_view layoutSubviews ];

  _gsg = new IPhoneGraphicsStateGuardian(_engine, _pipe, NULL);

  CGRect bounds = [_gl_view bounds];

  _properties.set_size(bounds.size.width, bounds.size.height);
  _properties.set_foreground(true);
  _properties.set_minimized(false);
  _properties.set_open(true);
  _is_valid = true;

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::set_pointer_in_window
//       Access: Private
//  Description: Indicates the mouse pointer is seen within the
//               window.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
set_pointer_in_window(int x, int y) {
  _input_devices[0].set_pointer_in_window(x, y);
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::set_pointer_out_of_window
//       Access: Private
//  Description: Indicates the mouse pointer is no longer within the
//               window.
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
set_pointer_out_of_window() {
  _input_devices[0].set_pointer_out_of_window();
}

////////////////////////////////////////////////////////////////////
//     Function: IPhoneGraphicsWindow::handle_button_delta
//       Access: Private
//  Description: Used to emulate button events
////////////////////////////////////////////////////////////////////
void IPhoneGraphicsWindow::
handle_button_delta(int num_touches) {
  // For now, we'll just map the number of touches to the mouse button
  // number.  1 touch is button 1, 2 touches is button 3 (because this
  // is the normal secondary button), and 3 touches is button 2.

  // This is just a cheesy remapping that will assist migrating
  // applications from standard PC's to iPhone.  It also works well
  // enough within the existing Panda mouse-input framework.  We
  // should expose the full multitouch functionality eventually, but
  // really, the whole mouse-input framework needs a bit of a
  // redesign.

  int new_buttons;
  switch (num_touches) {
  case 0:
    new_buttons = 0x00;
    break;
  case 1:
    new_buttons = 0x01;
    break;
  case 2:
    new_buttons = 0x04;
    break;
  case 3:
  default:
    new_buttons = 0x02;
    break;
  }

  int changed = _last_buttons ^ new_buttons;

  if (changed & 0x01) {
    if (new_buttons & 0x01) {
      _input_devices[0].button_down(MouseButton::one());
    } else {
      _input_devices[0].button_up(MouseButton::one());
    }
  }

  if (changed & 0x04) {
    if (new_buttons & 0x04) {
      _input_devices[0].button_down(MouseButton::two());
    } else {
      _input_devices[0].button_up(MouseButton::two());
    }
  }

  if (changed & 0x02) {
    if (new_buttons & 0x02) {
      _input_devices[0].button_down(MouseButton::three());
    } else {
      _input_devices[0].button_up(MouseButton::three());
    }
  }

  _last_buttons = new_buttons;
}
