/*
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_SELECTABLE_H)
#define INCLUDED_SELECTABLE_H

#include <cstddef>

#include "math/vector.h"
#include "scenelib.h"

class SelectionIntersection
{
  float m_depth;
  float m_distance;
public:
  SelectionIntersection() : m_depth(1), m_distance(2)
  {
  }
  SelectionIntersection(float depth, float distance) : m_depth(depth), m_distance(distance)
  {
  }
  bool operator<(const SelectionIntersection& other) const
  {
    if(m_distance != other.m_distance)
    {
      return m_distance < other.m_distance;
    }
    if(m_depth != other.m_depth)
    {
      return m_depth < other.m_depth;
    }
    return false;
  }
  bool equalEpsilon(const SelectionIntersection& other, float distanceEpsilon, float depthEpsilon) const
  {
    return float_equal_epsilon(m_distance, other.m_distance, distanceEpsilon)
      && float_equal_epsilon(m_depth, other.m_depth, depthEpsilon);
  }
  float depth() const
  {
    return m_depth;
  }
  bool valid() const
  {
    return depth() < 1;
  }
};

// returns true if self is closer than other
inline bool SelectionIntersection_closer(const SelectionIntersection& self, const SelectionIntersection& other)
{
  return self < other;
}

// assigns other to best if other is closer than best
inline void assign_if_closer(SelectionIntersection& best, const SelectionIntersection& other)
{
  if(SelectionIntersection_closer(other, best))
  {
    best = other;
  }
}




class VertexPointer
{
  typedef const unsigned char* byte_pointer;
public:
  typedef float elem_type;
  typedef const elem_type* pointer;
  typedef const elem_type& reference;

  class iterator
  {
  public:
    iterator() {}
    iterator(byte_pointer vertices, std::size_t stride)
      : m_iter(vertices), m_stride(stride) {}

    bool operator==(const iterator& other) const
    {
      return m_iter == other.m_iter;
    }
    bool operator!=(const iterator& other) const
    {
      return !operator==(other);
    }

    iterator operator+(std::size_t i)
    {
      return iterator(m_iter + i * m_stride, m_stride);
    }
    iterator operator+=(std::size_t i)
    {
      m_iter += i * m_stride;
      return *this;
    }
    iterator& operator++()
    {
      m_iter += m_stride;
      return *this;
    }
    iterator operator++(int)
    {
      iterator tmp = *this;
      m_iter += m_stride;
      return tmp;
    }
    reference operator*() const
    {
      return *reinterpret_cast<pointer>(m_iter);
    }
  private:
    byte_pointer m_iter;
    std::size_t m_stride;
  };

  VertexPointer(pointer vertices, std::size_t stride)
    : m_vertices(reinterpret_cast<byte_pointer>(vertices)), m_stride(stride) {}

  iterator begin() const
  {
    return iterator(m_vertices, m_stride);
  }

  reference operator[](std::size_t i) const
  {
    return *reinterpret_cast<pointer>(m_vertices + m_stride*i);
  }

private:
  byte_pointer m_vertices;
  std::size_t m_stride;
};

class IndexPointer
{
public:
  typedef unsigned int index_type;
  typedef const index_type* pointer;

  class iterator
  {
  public:
    iterator(pointer iter) : m_iter(iter) {}

    bool operator==(const iterator& other) const
    {
      return m_iter == other.m_iter;
    }
    bool operator!=(const iterator& other) const
    {
      return !operator==(other);
    }

    iterator operator+(std::size_t i)
    {
      return m_iter + i;
    }
    iterator operator+=(std::size_t i)
    {
      return m_iter += i;
    }
    iterator operator++()
    {
      return ++m_iter;
    }
    iterator operator++(int)
    {
      return m_iter++;
    }
    const index_type& operator*() const
    {
      return *m_iter;
    }
  private:
    void increment()
    {
      ++m_iter;
    }
    pointer m_iter;
  };

  IndexPointer(pointer indices, std::size_t count)
    : m_indices(indices), m_finish(indices + count) {}

  iterator begin() const
  {
    return m_indices;
  }
  iterator end() const
  {
    return m_finish;
  }

private:
  pointer m_indices;
  pointer m_finish;
};

template<typename Element> class BasicVector3;
typedef BasicVector3<float> Vector3;
class Matrix4;
class VolumeTest;

class SelectionTest
{
public:
  virtual void BeginMesh(const Matrix4& localToWorld, bool twoSided = false) = 0;
  virtual const VolumeTest& getVolume() const = 0;
  virtual const Vector3& getNear() const = 0;
  virtual const Vector3& getFar() const = 0;
  virtual void TestPoint(const Vector3& point, SelectionIntersection& best) = 0;
  virtual void TestPolygon(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) = 0;
  virtual void TestLineLoop(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) = 0;
  virtual void TestLineStrip(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) = 0;
  virtual void TestLines(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) = 0;
  virtual void TestTriangles(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) = 0; 
  virtual void TestQuads(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) = 0; 
  virtual void TestQuadStrip(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) = 0; 
};

class Selectable;

class Selector
{
public:
  virtual void pushSelectable(Selectable& selectable) = 0;
  virtual void popSelectable() = 0;
  virtual void addIntersection(const SelectionIntersection& intersection) = 0;
};

inline void Selector_add(Selector& selector, Selectable& selectable)
{
  selector.pushSelectable(selectable);
  selector.addIntersection(SelectionIntersection(0, 0));
  selector.popSelectable();
}

inline void Selector_add(Selector& selector, Selectable& selectable, const SelectionIntersection& intersection)
{
  selector.pushSelectable(selectable);
  selector.addIntersection(intersection);
  selector.popSelectable();
}


class VolumeTest;
class SelectionTestable
{
public:
  STRING_CONSTANT(Name, "SelectionTestable");

  virtual void testSelect(Selector& selector, SelectionTest& test) = 0;
};

inline SelectionTestable* Instance_getSelectionTestable(scene::Instance& instance)
{
  return InstanceTypeCast<SelectionTestable>::cast(instance);
}


template<typename FirstArgument>
class Callback1;
class Plane3;
typedef Callback1<const Plane3&> PlaneCallback;

class SelectedPlanes
{
public:
  virtual bool contains(const Plane3& plane) const = 0;
};

class PlaneSelectable
{
public:
  STRING_CONSTANT(Name, "PlaneSelectable");

  virtual void selectPlanes(Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback) = 0;
  virtual void selectReversedPlanes(Selector& selector, const SelectedPlanes& selectedPlanes) = 0;
};



#endif
