/*
    Copyright 2008 Intel Corporation
 
    Use, modification and distribution are subject to the Boost Software License,
    Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
    http://www.boost.org/LICENSE_1_0.txt).
*/
#ifndef BOOST_POLYGON_TRANSFORM_HPP
#define BOOST_POLYGON_TRANSFORM_HPP
#include "isotropy.hpp"
#include "point_3d_concept.hpp"
namespace boost { namespace polygon{
// Transformation of Coordinate Systems
// Enum meaning:
// Select which direction_3d to change the positive direction of each
// axis in the old coordinate system to map it to the new coordiante system.
// The first direction_3d listed for each enum is the direction to map the
// positive horizontal direction to.
// The second direction_3d listed for each enum is the direction to map the
// positive vertical direction to.
// The third direction_3d listed for each enum is the direction to map the
// positive proximal direction to.
// The zero position bit (LSB) indicates whether the horizontal axis flips
// when transformed.
// The 1st postion bit indicates whether the vertical axis flips when 
// transformed.
// The 2nd position bit indicates whether the horizontal and vertical axis
// swap positions when transformed.
// Note that the first eight values are the complete set of 2D transforms.
// The 3rd position bit indicates whether the proximal axis flips when
// transformed.
// The 4th position bit indicates whether the proximal and horizontal axis are
// swapped when transformed.  It changes the meaning of the 2nd position bit
// to mean that the horizontal and vertical axis are swapped in their new
// positions, naturally.
// The 5th position bit (MSB) indicates whether the proximal and vertical axis
// are swapped when transformed.  It is mutually exclusive with the 4th postion
// bit, making the maximum legal value 48 (decimal).  It similarly changes the
// meaning of the 2nd position bit to mean that the horizontal and vertical are
// swapped in their new positions.
// Enum Values:
// 000000 EAST NORTH UP 
// 000001 WEST NORTH UP 
// 000010 EAST SOUTH UP 
// 000011 WEST SOUTH UP 
// 000100 NORTH EAST UP 
// 000101 SOUTH EAST UP 
// 000110 NORTH WEST UP 
// 000111 SOUTH WEST UP 
// 001000 EAST NORTH DOWN 
// 001001 WEST NORTH DOWN 
// 001010 EAST SOUTH DOWN 
// 001011 WEST SOUTH DOWN 
// 001100 NORTH EAST DOWN 
// 001101 SOUTH EAST DOWN 
// 001110 NORTH WEST DOWN 
// 001111 SOUTH WEST DOWN 
// 010000 UP NORTH EAST 
// 010001 DOWN NORTH EAST 
// 010010 UP SOUTH EAST 
// 010011 DOWN SOUTH EAST 
// 010100 NORTH UP EAST 
// 010101 SOUTH UP EAST 
// 010110 NORTH DOWN EAST 
// 010111 SOUTH DOWN EAST 
// 011000 UP NORTH WEST 
// 011001 DOWN NORTH WEST 
// 011010 UP SOUTH WEST 
// 011011 DOWN SOUTH WEST 
// 011100 NORTH UP WEST 
// 011101 SOUTH UP WEST 
// 011110 NORTH DOWN WEST 
// 011111 SOUTH DOWN WEST 
// 100000 EAST UP NORTH 
// 100001 WEST UP NORTH 
// 100010 EAST DOWN NORTH 
// 100011 WEST DOWN NORTH 
// 100100 UP EAST NORTH 
// 100101 DOWN EAST NORTH 
// 100110 UP WEST NORTH 
// 100111 DOWN WEST NORTH 
// 101000 EAST UP SOUTH 
// 101001 WEST UP SOUTH 
// 101010 EAST DOWN SOUTH 
// 101011 WEST DOWN SOUTH 
// 101100 UP EAST SOUTH 
// 101101 DOWN EAST SOUTH 
// 101110 UP WEST SOUTH 
// 101111 DOWN WEST SOUTH 
class axis_transformation {
public:
  // Enum Names and values
  // NULL_TRANSFORM = 0, BEGIN_TRANSFORM = 0,
  // ENU = 0, EAST_NORTH_UP = 0, EN = 0, EAST_NORTH = 0, 
  // WNU = 1, WEST_NORTH_UP = 1, WN = 1, WEST_NORTH = 1, FLIP_X = 1,
  // ESU = 2, EAST_SOUTH_UP = 2, ES = 2, EAST_SOUTH = 2, FLIP_Y = 2,
  // WSU = 3, WEST_SOUTH_UP = 3, WS = 3, WEST_SOUTH = 3, 
  // NEU = 4, NORTH_EAST_UP = 4, NE = 4, NORTH_EAST = 4, SWAP_XY = 4,
  // SEU = 5, SOUTH_EAST_UP = 5, SE = 5, SOUTH_EAST = 5, 
  // NWU = 6, NORTH_WEST_UP = 6, NW = 6, NORTH_WEST = 6, 
  // SWU = 7, SOUTH_WEST_UP = 7, SW = 7, SOUTH_WEST = 7, 
  // END_2D_TRANSFORM = 7,
  // END = 8, EAST_NORTH_DOWN = 8, 
  // WND = 9, WEST_NORTH_DOWN = 9, 
  // ESD = 10, EAST_SOUTH_DOWN = 10, 
  // WSD = 11, WEST_SOUTH_DOWN = 11, 
  // NED = 12, NORTH_EAST_DOWN = 12, 
  // SED = 13, SOUTH_EAST_DOWN = 13, 
  // NWD = 14, NORTH_WEST_DOWN = 14, 
  // SWD = 15, SOUTH_WEST_DOWN = 15, 
  // UNE = 16, UP_NORTH_EAST = 16, 
  // DNE = 17, DOWN_NORTH_EAST = 17, 
  // USE = 18, UP_SOUTH_EAST = 18, 
  // DSE = 19, DOWN_SOUTH_EAST = 19, 
  // NUE = 20, NORTH_UP_EAST = 20, 
  // SUE = 21, SOUTH_UP_EAST = 21, 
  // NDE = 22, NORTH_DOWN_EAST = 22, 
  // SDE = 23, SOUTH_DOWN_EAST = 23, 
  // UNW = 24, UP_NORTH_WEST = 24, 
  // DNW = 25, DOWN_NORTH_WEST = 25, 
  // USW = 26, UP_SOUTH_WEST = 26, 
  // DSW = 27, DOWN_SOUTH_WEST = 27, 
  // NUW = 28, NORTH_UP_WEST = 28, 
  // SUW = 29, SOUTH_UP_WEST = 29, 
  // NDW = 30, NORTH_DOWN_WEST = 30, 
  // SDW = 31, SOUTH_DOWN_WEST = 31, 
  // EUN = 32, EAST_UP_NORTH = 32, 
  // WUN = 33, WEST_UP_NORTH = 33, 
  // EDN = 34, EAST_DOWN_NORTH = 34, 
  // WDN = 35, WEST_DOWN_NORTH = 35, 
  // UEN = 36, UP_EAST_NORTH = 36, 
  // DEN = 37, DOWN_EAST_NORTH = 37, 
  // UWN = 38, UP_WEST_NORTH = 38, 
  // DWN = 39, DOWN_WEST_NORTH = 39, 
  // EUS = 40, EAST_UP_SOUTH = 40, 
  // WUS = 41, WEST_UP_SOUTH = 41, 
  // EDS = 42, EAST_DOWN_SOUTH = 42, 
  // WDS = 43, WEST_DOWN_SOUTH = 43, 
  // UES = 44, UP_EAST_SOUTH = 44, 
  // DES = 45, DOWN_EAST_SOUTH = 45, 
  // UWS = 46, UP_WEST_SOUTH = 46, 
  // DWS = 47, DOWN_WEST_SOUTH = 47, END_TRANSFORM = 47 
  enum ATR {
    NULL_TRANSFORM = 0, BEGIN_TRANSFORM = 0,
    ENU = 0, EAST_NORTH_UP = 0, EN = 0, EAST_NORTH = 0, 
    WNU = 1, WEST_NORTH_UP = 1, WN = 1, WEST_NORTH = 1, FLIP_X       = 1,
    ESU = 2, EAST_SOUTH_UP = 2, ES = 2, EAST_SOUTH = 2, FLIP_Y       = 2,
    WSU = 3, WEST_SOUTH_UP = 3, WS = 3, WEST_SOUTH = 3, FLIP_XY      = 3,
    NEU = 4, NORTH_EAST_UP = 4, NE = 4, NORTH_EAST = 4, SWAP_XY      = 4,
    SEU = 5, SOUTH_EAST_UP = 5, SE = 5, SOUTH_EAST = 5, ROTATE_LEFT  = 5,
    NWU = 6, NORTH_WEST_UP = 6, NW = 6, NORTH_WEST = 6, ROTATE_RIGHT = 6,
    SWU = 7, SOUTH_WEST_UP = 7, SW = 7, SOUTH_WEST = 7, FLIP_SWAP_XY = 7, END_2D_TRANSFORM = 7,
    END = 8, EAST_NORTH_DOWN = 8, FLIP_Z = 8,
    WND = 9, WEST_NORTH_DOWN = 9, 
    ESD = 10, EAST_SOUTH_DOWN = 10, 
    WSD = 11, WEST_SOUTH_DOWN = 11, 
    NED = 12, NORTH_EAST_DOWN = 12, 
    SED = 13, SOUTH_EAST_DOWN = 13, 
    NWD = 14, NORTH_WEST_DOWN = 14, 
    SWD = 15, SOUTH_WEST_DOWN = 15, 
    UNE = 16, UP_NORTH_EAST = 16, 
    DNE = 17, DOWN_NORTH_EAST = 17, 
    USE = 18, UP_SOUTH_EAST = 18, 
    DSE = 19, DOWN_SOUTH_EAST = 19, 
    NUE = 20, NORTH_UP_EAST = 20, 
    SUE = 21, SOUTH_UP_EAST = 21, 
    NDE = 22, NORTH_DOWN_EAST = 22, 
    SDE = 23, SOUTH_DOWN_EAST = 23, 
    UNW = 24, UP_NORTH_WEST = 24, 
    DNW = 25, DOWN_NORTH_WEST = 25, 
    USW = 26, UP_SOUTH_WEST = 26, 
    DSW = 27, DOWN_SOUTH_WEST = 27, 
    NUW = 28, NORTH_UP_WEST = 28, 
    SUW = 29, SOUTH_UP_WEST = 29, 
    NDW = 30, NORTH_DOWN_WEST = 30, 
    SDW = 31, SOUTH_DOWN_WEST = 31, 
    EUN = 32, EAST_UP_NORTH = 32, 
    WUN = 33, WEST_UP_NORTH = 33, 
    EDN = 34, EAST_DOWN_NORTH = 34, 
    WDN = 35, WEST_DOWN_NORTH = 35, 
    UEN = 36, UP_EAST_NORTH = 36, 
    DEN = 37, DOWN_EAST_NORTH = 37, 
    UWN = 38, UP_WEST_NORTH = 38, 
    DWN = 39, DOWN_WEST_NORTH = 39, 
    EUS = 40, EAST_UP_SOUTH = 40, 
    WUS = 41, WEST_UP_SOUTH = 41, 
    EDS = 42, EAST_DOWN_SOUTH = 42, 
    WDS = 43, WEST_DOWN_SOUTH = 43, 
    UES = 44, UP_EAST_SOUTH = 44, 
    DES = 45, DOWN_EAST_SOUTH = 45, 
    UWS = 46, UP_WEST_SOUTH = 46, 
    DWS = 47, DOWN_WEST_SOUTH = 47, END_TRANSFORM = 47 
  };
  
  // Individual axis enum values indicate which axis an implicit individual
  // axis will be mapped to.
  // The value of the enum paired with an axis provides the information
  // about what the axis will transform to.
  // Three individual axis values, one for each axis, are equivalent to one
  // ATR enum value, but easier to work with because they are independent.
  // Converting to and from the individual axis values from the ATR value
  // is a convenient way to implement tranformation related functionality.
  // Enum meanings:
  // PX: map to positive x axis
  // NX: map to negative x axis
  // PY: map to positive y axis
  // NY: map to negative y axis
  // PZ: map to positive z axis
  // NZ: map to negative z axis
  enum INDIVIDUAL_AXIS {
    PX = 0,
    NX = 1,
    PY = 2,
    NY = 3,
    PZ = 4,
    NZ = 5
  };

  inline axis_transformation() : atr_(NULL_TRANSFORM) {}
  inline axis_transformation(ATR atr) : atr_(atr) {}
  inline axis_transformation(const axis_transformation& atr) : atr_(atr.atr_) {}
  explicit axis_transformation(const orientation_3d& orient);
  explicit axis_transformation(const direction_3d& dir);
  explicit axis_transformation(const orientation_2d& orient);
  explicit axis_transformation(const direction_2d& dir);

  // assignment operator 
  axis_transformation& operator=(const axis_transformation& a);

  // assignment operator 
  axis_transformation& operator=(const ATR& atr);

  // equivalence operator
  bool operator==(const axis_transformation& a) const;

  // inequivalence operator
  bool operator!=(const axis_transformation& a) const;

  // ordering
  bool operator<(const axis_transformation& a) const;

  // concatenation operator 
  axis_transformation operator+(const axis_transformation& a) const;

  // concatenate this with that
  axis_transformation& operator+=(const axis_transformation& a);

  // populate_axis_array writes the three INDIVIDUAL_AXIS values that the
  // ATR enum value of 'this' represent into axis_array
  void populate_axis_array(INDIVIDUAL_AXIS axis_array[]) const;

  // it is recommended that the directions stored in an array
  // in the caller code for easier isotropic access by orientation value
  inline void get_directions(direction_2d& horizontal_dir,
                             direction_2d& vertical_dir) const {
    bool bit2 = (atr_ & 4) != 0;
    bool bit1 = (atr_ & 2) != 0;
    bool bit0 = (atr_ & 1) != 0;      
    vertical_dir = direction_2d((direction_2d_enum)(((int)(!bit2) << 1) + !bit1));
    horizontal_dir = direction_2d((direction_2d_enum)(((int)(bit2) << 1) + !bit0));
  }

  // it is recommended that the directions stored in an array
  // in the caller code for easier isotropic access by orientation value
  inline void get_directions(direction_3d& horizontal_dir,
                             direction_3d& vertical_dir,
                             direction_3d& proximal_dir) const {
    bool bit5 = (atr_ & 32) != 0;
    bool bit4 = (atr_ & 16) != 0;
    bool bit3 = (atr_ & 8) != 0;
    bool bit2 = (atr_ & 4) != 0;
    bool bit1 = (atr_ & 2) != 0;
    bool bit0 = (atr_ & 1) != 0;   
    proximal_dir = direction_3d((direction_2d_enum)((((int)(!bit4 & !bit5)) << 2) +
                                                    ((int)(bit5) << 1) + 
                                                    !bit3));
    vertical_dir = direction_3d((direction_2d_enum)((((int)((bit4 & bit2) | (bit5 & !bit2))) << 2)+
                                                    ((int)(!bit5 & !bit2) << 1) + 
                                                    !bit1));
    horizontal_dir = direction_3d((direction_2d_enum)((((int)((bit5 & bit2) | 
                                                              (bit4 & !bit2))) << 2) +
                                                      ((int)(bit2 & !bit5) << 1) + 
                                                      !bit0));
  }
  
  // combine_axis_arrays concatenates this_array and that_array overwriting
  // the result into this_array
  static void combine_axis_arrays (INDIVIDUAL_AXIS this_array[],
                                   const INDIVIDUAL_AXIS that_array[]);

  // write_back_axis_array converts an array of three INDIVIDUAL_AXIS values
  // to the ATR enum value and sets 'this' to that value
  void write_back_axis_array(const INDIVIDUAL_AXIS this_array[]);

  // behavior is deterministic but undefined in the case where illegal
  // combinations of directions are passed in. 
  axis_transformation& set_directions(const direction_2d& horizontal_dir,
                                 const direction_2d& vertical_dir);
  // behavior is deterministic but undefined in the case where illegal
  // combinations of directions are passed in.
  axis_transformation& set_directions(const direction_3d& horizontal_dir,
                                 const direction_3d& vertical_dir,
                                 const direction_3d& proximal_dir);

  // transform the two coordinates by reference using the 2D portion of this
  template <typename coordinate_type>
  void transform(coordinate_type& x, coordinate_type& y) const;

  // transform the three coordinates by reference
  template <typename coordinate_type>
  void transform(coordinate_type& x, coordinate_type& y, coordinate_type& z) const;

  // invert the 2D portion of this
  axis_transformation& invert_2d();

  // get the inverse of the 2D portion of this
  axis_transformation inverse_2d() const;

  // invert this axis_transformation
  axis_transformation& invert();

  // get the inverse axis_transformation of this
  axis_transformation inverse() const;

  //friend std::ostream& operator<< (std::ostream& o, const axis_transformation& r);
  //friend std::istream& operator>> (std::istream& i, axis_transformation& r);

private:
  ATR atr_;
};


// Scaling object to be used to store the scale factor for each axis

// For use by the transformation object, in that context the scale factor
// is the amount that each axis scales by when transformed.
// If the horizontal value of the Scale is 10 that means the horizontal
// axis of the input is multiplied by 10 when the transformation is applied.
template <typename scale_factor_type>
class anisotropic_scale_factor {
public:
  inline anisotropic_scale_factor()
#ifndef BOOST_POLYGON_MSVC
    : scale_() 
#endif
  {
    scale_[0] = 1;
    scale_[1] = 1;
    scale_[2] = 1;
  } 
  inline anisotropic_scale_factor(scale_factor_type xscale, scale_factor_type yscale)
#ifndef BOOST_POLYGON_MSVC
    : scale_() 
#endif 
  {
    scale_[0] = xscale;
    scale_[1] = yscale;
    scale_[2] = 1;
  } 
  inline anisotropic_scale_factor(scale_factor_type xscale, scale_factor_type yscale, scale_factor_type zscale) 
#ifndef BOOST_POLYGON_MSVC
    : scale_() 
#endif   
  {
    scale_[0] = xscale;
    scale_[1] = yscale;
    scale_[2] = zscale;
  } 

  // get a component of the anisotropic_scale_factor by orientation
  scale_factor_type get(orientation_3d orient) const;
  scale_factor_type get(orientation_2d orient) const { return get(orientation_3d(orient)); }

  // set a component of the anisotropic_scale_factor by orientation
  void set(orientation_3d orient, scale_factor_type value);
  void set(orientation_2d orient, scale_factor_type value) { set(orientation_3d(orient), value); }

  scale_factor_type x() const;
  scale_factor_type y() const;
  scale_factor_type z() const;
  void x(scale_factor_type value);
  void y(scale_factor_type value);
  void z(scale_factor_type value);

  // concatination operator (convolve scale factors)
  anisotropic_scale_factor operator+(const anisotropic_scale_factor& s) const;

  // concatinate this with that
  const anisotropic_scale_factor& operator+=(const anisotropic_scale_factor& s);

  // transform this scale with an axis_transform
  anisotropic_scale_factor& transform(axis_transformation atr);

  // scale the two coordinates
  template <typename coordinate_type>
  void scale(coordinate_type& x, coordinate_type& y) const;

  // scale the three coordinates
  template <typename coordinate_type>
  void scale(coordinate_type& x, coordinate_type& y, coordinate_type& z) const;

  // invert this scale factor to give the reverse scale factor
  anisotropic_scale_factor& invert(); 

private:
  scale_factor_type scale_[3];

  //friend std::ostream& operator<< (std::ostream& o, const Scale& r);
  //friend std::istream& operator>> (std::istream& i, Scale& r);
};

// Transformation object, stores and provides services for transformations

// Transformation object stores an axistransformation, a scale factor and a translation.
// The tranlation is the position of the origin of the new system of coordinates in the old system.
// The scale scales the coordinates before they are transformed.
template <typename coordinate_type>
class transformation {
public:
  transformation();
  transformation(axis_transformation atr);
  transformation(axis_transformation::ATR atr);
  template <typename point_type>
  transformation(const point_type& p);
  template <typename point_type>
  transformation(axis_transformation atr, const point_type& p);
  template <typename point_type>
  transformation(axis_transformation atr, const point_type& referencePt, const point_type& destinationPt);
  transformation(const transformation& tr);

  // equivalence operator 
  bool operator==(const transformation& tr) const;

  // inequivalence operator 
  bool operator!=(const transformation& tr) const;

  // ordering
  bool operator<(const transformation& tr) const;

  // concatenation operator 
  transformation operator+(const transformation& tr) const;

  // concatenate this with that
  const transformation& operator+=(const transformation& tr);

  // get the axis_transformation portion of this
  inline axis_transformation get_axis_transformation() const {return atr_;}

  // set the axis_transformation portion of this
  void set_axis_transformation(const axis_transformation& atr);

  // get the translation portion of this as a point3d
  template <typename point_type>
  void get_translation(point_type& translation) const;

  // set the translation portion of this with a point3d
  template <typename point_type>
  void set_translation(const point_type& p);

  // apply the 2D portion of this transformation to the two coordinates given
  void transform(coordinate_type& x, coordinate_type& y) const;

  // apply this transformation to the three coordinates given
  void transform(coordinate_type& x, coordinate_type& y, coordinate_type& z) const;

  // invert this transformation
  transformation& invert();
    
  // get the inverse of this transformation
  transformation inverse() const;

  inline void get_directions(direction_2d& horizontal_dir,
                             direction_2d& vertical_dir) const {
    return atr_.get_directions(horizontal_dir, vertical_dir); }

  inline void get_directions(direction_3d& horizontal_dir,
                             direction_3d& vertical_dir,
                             direction_3d& proximal_dir) const {
    return atr_.get_directions(horizontal_dir, vertical_dir, proximal_dir); }

private:
  axis_transformation atr_;
  point_3d_data<coordinate_type> p_;

  template <typename point_type>
  void construct_dispatch(axis_transformation atr, point_type p, point_concept tag);
  template <typename point_type>
  void construct_dispatch(axis_transformation atr, point_type p, point_3d_concept tag);
  template <typename point_type>
  void construct_dispatch(axis_transformation atr, point_type rp, point_type dp, point_concept tag);
  template <typename point_type>
  void construct_dispatch(axis_transformation atr, point_type rp, point_type dp, point_3d_concept tag);

  //friend std::ostream& operator<< (std::ostream& o, const transformation& tr);
  //friend std::istream& operator>> (std::istream& i, transformation& tr);
};
}
}
#include "detail/transform_detail.hpp"
#endif