/******************************************************************************
 *  Wormux is a convivial mass murder game.
 *  Copyright (C) 2001-2010 Wormux Team.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 ******************************************************************************
 * Mouse management
 *****************************************************************************/

#include "interface/mouse.h"

#include "interface/cursor.h"
#include "interface/interface.h"
#include "interface/mouse_cursor.h"
#include "character/character.h"
#include "game/config.h"
#include "game/game_mode.h"
#include "game/game.h"
#include "graphic/video.h"
#include "include/app.h"
#include "include/action_handler.h"
#include "map/camera.h"
#include "map/map.h"
#include "team/macro.h"
#include "team/team.h"
#include <WORMUX_point.h>
#include "tool/resource_manager.h"
#include "weapon/weapon.h"
#include "game/time.h"

std::string __pointers[] = {
  "mouse/pointer_standard",
  "mouse/pointer_select",
  "mouse/pointer_move",
  "mouse/pointer_arrow_up",
  "mouse/pointer_arrow_up_right",
  "mouse/pointer_arrow_up_left",
  "mouse/pointer_arrow_down",
  "mouse/pointer_arrow_down_right",
  "mouse/pointer_arrow_down_left",
  "mouse/pointer_arrow_right",
  "mouse/pointer_arrow_left",
  "mouse/pointer_aim",
  "mouse/pointer_attack_from_left",
  "mouse/pointer_attack_from_right",
};
std::map<Mouse::pointer_t, MouseCursor> Mouse::cursors;

Mouse::Mouse():
  lastpos(-1,-1),
  last_hide_time(0)
{
  visible = MOUSE_VISIBLE;

  // Load the different pointers
  Profile *res = GetResourceManager().LoadXMLProfile("cursors.xml", false);

  for (int i=POINTER_SELECT; i < POINTER_ATTACK; i++) {
    cursors.insert(std::make_pair(Mouse::pointer_t(i),
				  GetResourceManager().LoadMouseCursor(res, __pointers[i],
								   Mouse::pointer_t(i))));
  }

  current_pointer = POINTER_STANDARD;
  GetResourceManager().UnLoadXMLProfile(res);
}

bool Mouse::HasFocus() const
{
  Uint8 state = SDL_GetAppState();

  if ((state & SDL_APPMOUSEFOCUS) &&
      (state & SDL_APPINPUTFOCUS) &&
      (state & SDL_APPACTIVE)) {
    return true;
  }

  return false;
}

void Mouse::ActionLeftClic(bool) const
{
  const Point2i pos_monde = GetWorldPosition();

  // Action in weapon menu ?
  if( Interface::GetInstance()->weapons_menu.ActionClic( GetPosition() ) )
    return;

  //Change character by mouse click only if the choosen weapon allows it
  if( GameMode::GetInstance()->AllowCharacterSelection() &&
      ActiveTeam().GetWeapon().mouse_character_selection) {

    // Choose a character of our own team
    bool character_found=false;
    Team::iterator it = ActiveTeam().begin(),
                         end = ActiveTeam().end();

    for( ; it != end; ++it) {
      if( &(*it) != &ActiveCharacter()
        && !it -> IsDead()
        && it->GetRect().Contains( pos_monde ) ){

        character_found = true;
        break;
      }
    }

    if (character_found) {
      while ( &(*it) != &ActiveCharacter() )
        ActiveTeam().NextCharacter ();

      Action * next_character = new Action(Action::ACTION_PLAYER_CHANGE_CHARACTER);
      uint next_character_index = (*it).GetCharacterIndex();
      next_character->Push((int)next_character_index);
      ActionHandler::GetInstance()->NewAction(next_character);

      return;
    }

    if( ActiveCharacter().GetRect().Contains( pos_monde ) ){
      CharacterCursor::GetInstance()->FollowActiveCharacter();
      return;
    }
  }

  // Choosing target for a weapon, many posibilities :
  // - Do nothing
  // - Choose a target but don't fire
  // - Choose a target and fire it !
  Action* a = new Action(Action::ACTION_WEAPON_SET_TARGET);
  a->Push(GetWorldPosition());
  ActionHandler::GetInstance()->NewAction (a);
}


void Mouse::ActionRightClic(bool) const
{
  Interface::GetInstance()->weapons_menu.SwitchDisplay();
}

void Mouse::ActionWheelUp(bool shift) const
{
  ActiveTeam().AccessWeapon().HandleMouseWheelUp(shift);
}

void Mouse::ActionWheelDown(bool shift) const
{
  ActiveTeam().AccessWeapon().HandleMouseWheelDown(shift);
}

Uint8 Mouse::BUTTON_RIGHT() // static method
{
  if (Config::GetRef().GetLeftHandedMouse())
    return SDL_BUTTON_LEFT;

  return SDL_BUTTON_RIGHT;
}

Uint8 Mouse::BUTTON_LEFT() // static method
{
  if (Config::GetRef().GetLeftHandedMouse())
    return SDL_BUTTON_RIGHT;

  return SDL_BUTTON_LEFT;
}

bool Mouse::HandleClic (const SDL_Event& event) const
{
  if (!HasFocus()) {
    return false;
  }

  if ( event.type != SDL_MOUSEBUTTONDOWN &&
       event.type != SDL_MOUSEBUTTONUP ) {
    return false;
  }

  if (Game::GetInstance()->ReadState() != Game::PLAYING)
    return true;

  if(!ActiveTeam().IsLocalHuman())
    return true;

  if( event.type == SDL_MOUSEBUTTONDOWN ){
    bool shift = !!(SDL_GetModState() & KMOD_SHIFT);

    if (event.button.button == Mouse::BUTTON_RIGHT())
      ActionRightClic(shift);
    else if (event.button.button == Mouse::BUTTON_LEFT())
      ActionLeftClic(shift);
    else if (event.button.button == SDL_BUTTON_WHEELDOWN)
      ActionWheelDown(shift);
    else if (event.button.button == SDL_BUTTON_WHEELUP)
      ActionWheelUp(shift);
  }
  return true;
}

void Mouse::GetDesignatedCharacter() const
{
  if (Game::GetInstance()->ReadState() != Game::PLAYING)
    return;

  const Point2i pos_monde = GetWorldPosition();

  // Which character is pointed by the mouse ? (appart from the active one)
  Interface::GetInstance()->character_under_cursor = NULL;
  FOR_ALL_LIVING_CHARACTERS(team, character){
    if ((&(*character) != &ActiveCharacter())
       && character->GetRect().Contains(pos_monde) ){
      Interface::GetInstance()->character_under_cursor = &(*character);
    }
  }

  // No character is pointed... what about the active one ?
  if ((Interface::GetInstance()->character_under_cursor == NULL)
      && ActiveCharacter().GetRect().Contains( pos_monde)){
      Interface::GetInstance()->character_under_cursor = &ActiveCharacter();
  }

}

void Mouse::Refresh()
{
  /* FIXME the 200 is hardcoded because I cannot find where the main loop
   * refresh rate is set... */
#define NB_LOOP_BEFORE_HIDE 200
  static int counter = 0;
  GetDesignatedCharacter();

  Point2i pos = GetPosition();

  if (lastpos != pos)
    {
      Show();
      lastpos = pos;
      counter = NB_LOOP_BEFORE_HIDE;
      ShowGameInterface();
    }
  else
    if (visible == MOUSE_VISIBLE)
      /* The mouse is hidden after a while when not moving */
      if (--counter <= 0)
        Hide();
}

Point2i Mouse::GetPosition() const
{
  int x, y;

  SDL_GetMouseState( &x, &y);
  return Point2i(x, y);
}

Point2i Mouse::GetWorldPosition() const
{
  return GetPosition() + Camera::GetInstance()->GetPosition();
}

MouseCursor& Mouse::GetCursor(pointer_t pointer) const
{
  ASSERT(pointer != POINTER_STANDARD);

  if (pointer == POINTER_ATTACK) {
    if (ActiveCharacter().GetDirection() == DIRECTION_RIGHT)
      return GetCursor(POINTER_ATTACK_FROM_LEFT);
    else
      return GetCursor(POINTER_ATTACK_FROM_RIGHT);
  }
  return (*cursors.find(pointer)).second;
}

Mouse::pointer_t Mouse::GetPointer() const
{
  return current_pointer;
}

// set the new pointer type and return the previous one
Mouse::pointer_t Mouse::SetPointer(pointer_t pointer)
{
  if (Config::GetInstance()->GetDefaultMouseCursor()) {
    Show(); // be sure cursor is visible
    return Mouse::POINTER_STANDARD;
  }

  if (current_pointer == pointer) return pointer;

  if (pointer == POINTER_STANDARD)
    SDL_ShowCursor(true);
  else
    SDL_ShowCursor(false);

  pointer_t old_pointer = current_pointer;
  current_pointer = pointer;

  return old_pointer;
}

void Mouse::Draw() const
{
  if (visible != MOUSE_VISIBLE)
    return;

  if (current_pointer == POINTER_STANDARD)
    return; // use standard SDL cursor

  const MouseCursor& cursor = GetCursor(current_pointer);
  const Surface& surf = cursor.GetSurface();
  GetMainWindow().Blit(surf, GetPosition() - cursor.GetClicPos());
  GetWorld().ToRedrawOnScreen(Rectanglei(GetPosition().x - cursor.GetClicPos().x,
                                    GetPosition().y - cursor.GetClicPos().y,
				    surf.GetWidth(), surf.GetHeight()));
}

void Mouse::Show()
{
  if(((Time::GetInstance()->Read()-last_hide_time) > 10000) && (visible == MOUSE_HIDDEN))
  {
      CenterPointer();
  }
  visible = MOUSE_VISIBLE;

  if (Config::GetInstance()->GetDefaultMouseCursor()) {
    SDL_ShowCursor(true); // be sure cursor is visible
  }

}

void Mouse::Hide()
{
  if(visible == MOUSE_VISIBLE)
  {
    last_hide_time = Time::GetInstance()->Read();
  }
  visible = MOUSE_HIDDEN;
  SDL_ShowCursor(false); // be sure cursor is invisible

}

// Center the pointer on the screen
void Mouse::CenterPointer()
{
  SetPosition(Point2i(GetMainWindow().GetWidth() / 2,
		      GetMainWindow().GetHeight() / 2));
}

void Mouse::SetPosition(Point2i pos)
{
  if (!HasFocus()) // The application has not the focus, don't move the mouse cursor!
    return;

  MSG_DEBUG("mouse", "1) %d, %d\n", GetPosition().GetX(), GetPosition().GetY());

  SDL_WarpMouse(pos.x, pos.y);
  SDL_PumpEvents(); // force new position else GetPosition does not return new position

  lastpos = GetPosition();

  MSG_DEBUG("mouse", "2) %d, %d\n", GetPosition().GetX(), GetPosition().GetY());
}
