/*
Copyright (C) 2001-2006, William Joseph.
All Rights Reserved.

This file is part of GtkRadiant.

GtkRadiant 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.

GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#if !defined(INCLUDED_TARGETABLE_H)
#define INCLUDED_TARGETABLE_H

#include <set>
#include <map>

#include "cullable.h"
#include "renderable.h"

#include "math/line.h"
#include "render.h"
#include "generic/callback.h"
#include "selectionlib.h"
#include "entitylib.h"
#include "eclasslib.h"
#include "stringio.h"

class Targetable
{
public:
  virtual const Vector3& world_position() const = 0;
};

typedef std::set<Targetable*> targetables_t;

extern const char* g_targetable_nameKey;

targetables_t* getTargetables(const char* targetname);

class EntityConnectionLine : public OpenGLRenderable
{
public:
  Vector3 start;
  Vector3 end;

  void render(RenderStateFlags state) const
  {
    float s1[2], s2[2];
    Vector3 dir(vector3_subtracted(end, start));
    double len = vector3_length(dir);
    vector3_scale(dir, 8.0 * (1.0 / len));
    s1[0] = dir[0] - dir[1];
    s1[1] = dir[0] + dir[1];
    s2[0] = dir[0] + dir[1];
    s2[1] = -dir[0] + dir[1];
    
    glBegin(GL_LINES);

    glVertex3fv(vector3_to_array(start));
    glVertex3fv(vector3_to_array(end));

    len*=0.0625; // half / 8

    Vector3 arrow(start);
    for (unsigned int i = 0, count = (len<32)? 1 : static_cast<unsigned int>(len*0.0625); i < count; i++)
    {
      vector3_add(arrow, vector3_scaled(dir, (len<32)?len:32));
      glVertex3fv(vector3_to_array(arrow));
      glVertex3f(arrow[0]+s1[0], arrow[1]+s1[1], arrow[2]+dir[2]);
      glVertex3fv(vector3_to_array(arrow));
      glVertex3f(arrow[0]+s2[0], arrow[1]+s2[1], arrow[2]+dir[2]);
    }
    
    glEnd();
  }
};

class TargetedEntity
{
  Targetable& m_targetable;
  targetables_t* m_targets;

  void construct()
  {
    if(m_targets != 0)
      m_targets->insert(&m_targetable);
  }
  void destroy()
  {
    if(m_targets != 0)
      m_targets->erase(&m_targetable);
  }
public:
  TargetedEntity(Targetable& targetable)
    : m_targetable(targetable), m_targets(getTargetables(""))
  {
    construct();
  }
  ~TargetedEntity()
  {
    destroy();
  }
  void targetnameChanged(const char* name)
  {
    destroy();
    m_targets = getTargetables(name);
    construct();
  }
  typedef MemberCaller1<TargetedEntity, const char*, &TargetedEntity::targetnameChanged> TargetnameChangedCaller;
};


class TargetingEntity
{
  targetables_t* m_targets;
public:
  TargetingEntity() :
    m_targets(getTargetables(""))
  {
  }
  void targetChanged(const char* target)
  {
    m_targets = getTargetables(target);
  }
  typedef MemberCaller1<TargetingEntity, const char*, &TargetingEntity::targetChanged> TargetChangedCaller;

  typedef targetables_t::iterator iterator;

  iterator begin() const
  {
    if(m_targets == 0)
    {
      return iterator();
    }
    return m_targets->begin();
  }
  iterator end() const
  {
    if(m_targets == 0)
    {
      return iterator();
    }
    return m_targets->end();
  }
  size_t size() const
  {
    if(m_targets == 0)
    {
      return 0;
    }
    return m_targets->size();
  }
  bool empty() const
  {
    return m_targets == 0 || m_targets->empty();
  }
};



template<typename Functor>
void TargetingEntity_forEach(const TargetingEntity& targets, const Functor& functor)
{
  for(TargetingEntity::iterator i = targets.begin(); i != targets.end(); ++i)
  {
    functor((*i)->world_position());
  }
}

typedef std::map<std::size_t, TargetingEntity> TargetingEntities;

template<typename Functor>
void TargetingEntities_forEach(const TargetingEntities& targetingEntities, const Functor& functor)
{
  for(TargetingEntities::const_iterator i = targetingEntities.begin(); i != targetingEntities.end(); ++i)
  {
    TargetingEntity_forEach((*i).second, functor);
  }
}

class TargetLinesPushBack
{
  RenderablePointVector& m_targetLines;
  const Vector3& m_worldPosition;
  const VolumeTest& m_volume;
public:
  TargetLinesPushBack(RenderablePointVector& targetLines, const Vector3& worldPosition, const VolumeTest& volume) :
    m_targetLines(targetLines), m_worldPosition(worldPosition), m_volume(volume)
  {
  }
  void operator()(const Vector3& worldPosition) const
  {
    if(m_volume.TestLine(segment_for_startend(m_worldPosition, worldPosition)))
    {
      m_targetLines.push_back(PointVertex(reinterpret_cast<const Vertex3f&>(m_worldPosition)));
      m_targetLines.push_back(PointVertex(reinterpret_cast<const Vertex3f&>(worldPosition)));
    }
  }
};

class TargetKeys : public Entity::Observer
{
  TargetingEntities m_targetingEntities;
  Callback m_targetsChanged;

  bool readTargetKey(const char* key, std::size_t& index)
  {
    if(string_equal_n(key, "target", 6))
    {
      index = 0;
      if(string_empty(key + 6) || string_parse_size(key + 6, index))
      {
        return true;
      }
    }
    if(string_equal(key, "killtarget"))
    {
      index = -1;
	  return true;
    }
    return false;
  }
public:
  void setTargetsChanged(const Callback& targetsChanged)
  {
    m_targetsChanged = targetsChanged;
  }
  void targetsChanged()
  {
    m_targetsChanged();
  }

  void insert(const char* key, EntityKeyValue& value)
  {
    std::size_t index;
    if(readTargetKey(key, index))
    {
      TargetingEntities::iterator i = m_targetingEntities.insert(TargetingEntities::value_type(index, TargetingEntity())).first;
      value.attach(TargetingEntity::TargetChangedCaller((*i).second));
      targetsChanged();
    }
  }
  void erase(const char* key, EntityKeyValue& value)
  {
    std::size_t index;
    if(readTargetKey(key, index))
    {
      TargetingEntities::iterator i = m_targetingEntities.find(index);
      value.detach(TargetingEntity::TargetChangedCaller((*i).second));
      m_targetingEntities.erase(i);
      targetsChanged();
    }
  }
  const TargetingEntities& get() const
  {
    return m_targetingEntities;
  }
};



class RenderableTargetingEntity
{
  TargetingEntity& m_targets;
  mutable RenderablePointVector m_target_lines;
public:
  static Shader* m_state;

  RenderableTargetingEntity(TargetingEntity& targets)
    : m_targets(targets), m_target_lines(GL_LINES)
  {
  }
  void compile(const VolumeTest& volume, const Vector3& world_position) const
  {
    m_target_lines.clear();
    m_target_lines.reserve(m_targets.size() * 2);
    TargetingEntity_forEach(m_targets, TargetLinesPushBack(m_target_lines, world_position, volume));
  }
  void render(Renderer& renderer, const VolumeTest& volume, const Vector3& world_position) const
  {
    if(!m_targets.empty())
    {
      compile(volume, world_position);
      if(!m_target_lines.empty())
      {
        renderer.addRenderable(m_target_lines, g_matrix4_identity);
      }
    }
  }
};

class RenderableTargetingEntities
{
  const TargetingEntities& m_targets;
  mutable RenderablePointVector m_target_lines;
public:
  static Shader* m_state;

  RenderableTargetingEntities(const TargetingEntities& targets)
    : m_targets(targets), m_target_lines(GL_LINES)
  {
  }
  void compile(const VolumeTest& volume, const Vector3& world_position) const
  {
    m_target_lines.clear();
    TargetingEntities_forEach(m_targets, TargetLinesPushBack(m_target_lines, world_position, volume));
  }
  void render(Renderer& renderer, const VolumeTest& volume, const Vector3& world_position) const
  {
    if(!m_targets.empty())
    {
      compile(volume, world_position);
      if(!m_target_lines.empty())
      {
        renderer.addRenderable(m_target_lines, g_matrix4_identity);
      }
    }
  }
};


class TargetableInstance :
public SelectableInstance,
public Targetable,
public Entity::Observer
{
  mutable Vertex3f m_position;
  EntityKeyValues& m_entity;
  TargetKeys m_targeting;
  TargetedEntity m_targeted;
  RenderableTargetingEntities m_renderable;
public:

  TargetableInstance(
    const scene::Path& path,
    scene::Instance* parent,
    void* instance,
    InstanceTypeCastTable& casts,
    EntityKeyValues& entity,
    Targetable& targetable
  ) :
    SelectableInstance(path, parent, instance, casts),
    m_entity(entity),
    m_targeted(targetable),
    m_renderable(m_targeting.get())
  {
    m_entity.attach(*this);
    m_entity.attach(m_targeting);
  }
  ~TargetableInstance()
  {
    m_entity.detach(m_targeting);
    m_entity.detach(*this);
  }

  void setTargetsChanged(const Callback& targetsChanged)
  {
    m_targeting.setTargetsChanged(targetsChanged);
  }
  void targetsChanged()
  {
    m_targeting.targetsChanged();
  }

  void insert(const char* key, EntityKeyValue& value)
  {
    if(string_equal(key, g_targetable_nameKey))
    {
      value.attach(TargetedEntity::TargetnameChangedCaller(m_targeted));
    }
  }
  void erase(const char* key, EntityKeyValue& value)
  {
    if(string_equal(key, g_targetable_nameKey))
    {
      value.detach(TargetedEntity::TargetnameChangedCaller(m_targeted));
    }
  }

  const Vector3& world_position() const
  {
#if 1
    const AABB& bounds = Instance::worldAABB();
    if(aabb_valid(bounds))
    {
      return bounds.origin;
    }
#else
    const AABB& childBounds = Instance::childBounds();
    if(aabb_valid(childBounds))
    {
      return childBounds.origin;
    }
#endif
    return vector4_to_vector3(localToWorld().t());
  }

  void render(Renderer& renderer, const VolumeTest& volume) const
  {
    renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly);
    renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eFullMaterials);
    m_renderable.render(renderer, volume, world_position());
  }

  const TargetingEntities& getTargeting() const
  {
    return m_targeting.get();
  }
};


class RenderableConnectionLines : public Renderable
{
  typedef std::set<TargetableInstance*> TargetableInstances;
  TargetableInstances m_instances;
public:
  void attach(TargetableInstance& instance)
  {
    ASSERT_MESSAGE(m_instances.find(&instance) == m_instances.end(), "cannot attach instance");
    m_instances.insert(&instance);
  }
  void detach(TargetableInstance& instance)
  {
    ASSERT_MESSAGE(m_instances.find(&instance) != m_instances.end(), "cannot detach instance");
    m_instances.erase(&instance);
  }

  void renderSolid(Renderer& renderer, const VolumeTest& volume) const
  {
    for(TargetableInstances::const_iterator i = m_instances.begin(); i != m_instances.end(); ++i)
    {
      if((*i)->path().top().get().visible())
      {
        (*i)->render(renderer, volume);
      }
    }
  }
  void renderWireframe(Renderer& renderer, const VolumeTest& volume) const
  {
    renderSolid(renderer, volume);
  }
};

typedef Static<RenderableConnectionLines> StaticRenderableConnectionLines;

#endif
