#ifndef DATE_TIME_PERIOD_HPP___

#define DATE_TIME_PERIOD_HPP___



/* Copyright (c) 2002,2003 CrystalClear Software, Inc.

 * Use, modification and distribution is subject to the 

 * Boost Software License, Version 1.0. (See accompanying

 * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)

 * Author: Jeff Garland, Bart Garst 

 * $Date: 2008-02-27 15:00:24 -0500 (Wed, 27 Feb 2008) $

 */



/*! \file period.hpp

  This file contain the implementation of the period abstraction. This is

  basically the same idea as a range.  Although this class is intended for

  use in the time library, it is pretty close to general enough for other

  numeric uses.



*/



#include "boost/operators.hpp"





namespace boost {

namespace date_time {

  //!Provides generalized period type useful in date-time systems

  /*!This template uses a class to represent a time point within the period

    and another class to represent a duration.  As a result, this class is

    not appropriate for use when the number and duration representation 

    are the same (eg: in the regular number domain).

    

    A period can be specified by providing either the begining point and 

    a duration or the begining point and the end point( end is NOT part 

    of the period but 1 unit past it. A period will be "invalid" if either

    end_point <= begin_point or the given duration is <= 0. Any valid period 

    will return false for is_null().

    

    Zero length periods are also considered invalid. Zero length periods are

    periods where the begining and end points are the same, or, the given 

    duration is zero. For a zero length period, the last point will be one 

    unit less than the begining point.



    In the case that the begin and last are the same, the period has a 

    length of one unit.

    

    The best way to handle periods is usually to provide a begining point and

    a duration.  So, day1 + 7 days is a week period which includes all of the

    first day and 6 more days (eg: Sun to Sat).



   */

  template<class point_rep, class duration_rep>

  class period : private

      boost::less_than_comparable<period<point_rep, duration_rep> 

    , boost::equality_comparable< period<point_rep, duration_rep> 

    > >

  {

  public:

    typedef point_rep point_type;

    typedef duration_rep duration_type;



    period(point_rep first_point, point_rep end_point);

    period(point_rep first_point, duration_rep len);

    point_rep begin() const;

    point_rep end() const;

    point_rep last() const;

    duration_rep length() const;

    bool is_null() const;

    bool operator==(const period& rhs) const;

    bool operator<(const period& rhs) const;

    void shift(const duration_rep& d);

    void expand(const duration_rep& d);

    bool contains(const point_rep& point) const;

    bool contains(const period& other) const;

    bool intersects(const period& other) const;

    bool is_adjacent(const period& other) const;

    bool is_before(const point_rep& point) const;

    bool is_after(const point_rep& point) const;

    period intersection(const period& other) const;

    period merge(const period& other) const;

    period span(const period& other) const;

  private:

    point_rep begin_;

    point_rep last_;

  };



  //! create a period from begin to last eg: [begin,end)

  /*! If end <= begin then the period will be invalid

   */

  template<class point_rep, class duration_rep>

  inline

  period<point_rep,duration_rep>::period(point_rep first_point, 

                                         point_rep end_point) : 

    begin_(first_point), 

    last_(end_point - duration_rep::unit())

  {}



  //! create a period as [begin, begin+len)

  /*! If len is <= 0 then the period will be invalid

   */

  template<class point_rep, class duration_rep>

  inline

  period<point_rep,duration_rep>::period(point_rep first_point, duration_rep len) :

    begin_(first_point), 

    last_(first_point + len-duration_rep::unit())

  { }





  //! Return the first element in the period

  template<class point_rep, class duration_rep>

  inline

  point_rep period<point_rep,duration_rep>::begin() const 

  {

    return begin_;

  }



  //! Return one past the last element 

  template<class point_rep, class duration_rep>

  inline

  point_rep period<point_rep,duration_rep>::end() const 

  {

    return last_ + duration_rep::unit();

  }



  //! Return the last item in the period

  template<class point_rep, class duration_rep>

  inline

  point_rep period<point_rep,duration_rep>::last() const 

  {

    return last_;

  }



  //! True if period is ill formed (length is zero or less)

  template<class point_rep, class duration_rep>

  inline

  bool period<point_rep,duration_rep>::is_null() const 

  {

    return end() <= begin_;

  }



  //! Return the length of the period

  template<class point_rep, class duration_rep>

  inline

  duration_rep period<point_rep,duration_rep>::length() const

  {

    if(last_ < begin_){ // invalid period

      return last_+duration_rep::unit() - begin_;

    }

    else{

      return end() - begin_; // normal case

    }

  }



  //! Equality operator

  template<class point_rep, class duration_rep>

  inline

  bool period<point_rep,duration_rep>::operator==(const period& rhs) const 

  {

    return  ((begin_ == rhs.begin_) && 

             (last_ == rhs.last_));

  }



  //! Strict as defined by rhs.last <= lhs.last

  template<class point_rep, class duration_rep>

  inline

  bool period<point_rep,duration_rep>::operator<(const period& rhs) const 

  {

    return (last_ < rhs.begin_);

  } 





  //! Shift the start and end by the specified amount

  template<class point_rep, class duration_rep>

  inline

  void period<point_rep,duration_rep>::shift(const duration_rep& d)

  {

    begin_ = begin_ + d;

    last_  = last_  + d;

  }



  /** Expands the size of the period by the duration on both ends.

   *

   *So before expand 

   *@code

   *

   *         [-------]

   * ^   ^   ^   ^   ^   ^  ^

   * 1   2   3   4   5   6  7

   * 

   *@endcode

   * After expand(2)

   *@code

   *

   * [----------------------]

   * ^   ^   ^   ^   ^   ^  ^

   * 1   2   3   4   5   6  7

   * 

   *@endcode

   */

  template<class point_rep, class duration_rep>

  inline

  void period<point_rep,duration_rep>::expand(const duration_rep& d)

  {

    begin_ = begin_ - d;

    last_  = last_  + d;

  }



  //! True if the point is inside the period, zero length periods contain no points

  template<class point_rep, class duration_rep>

  inline

  bool period<point_rep,duration_rep>::contains(const point_rep& point) const 

  {

    return ((point >= begin_) &&

            (point <= last_));

  }





  //! True if this period fully contains (or equals) the other period

  template<class point_rep, class duration_rep>

  inline

  bool period<point_rep,duration_rep>::contains(const period<point_rep,duration_rep>& other) const

  {

    return ((begin_ <= other.begin_) && (last_ >= other.last_));

  }





  //! True if periods are next to each other without a gap.

  /* In the example below, p1 and p2 are adjacent, but p3 is not adjacent

   * with either of p1 or p2.

   *@code

   *   [-p1-)

   *        [-p2-)

   *          [-p3-) 

   *@endcode

   */

  template<class point_rep, class duration_rep>

  inline

  bool 

  period<point_rep,duration_rep>::is_adjacent(const period<point_rep,duration_rep>& other) const 

  {

    return (other.begin() == end() ||

            begin_ == other.end());

  }





  //! True if all of the period is prior or t < start

  /* In the example below only point 1 would evaluate to true.

   *@code

   *     [---------])

   * ^   ^    ^     ^   ^

   * 1   2    3     4   5

   * 

   *@endcode

   */

  template<class point_rep, class duration_rep>

  inline

  bool 

  period<point_rep,duration_rep>::is_after(const point_rep& t) const 

  { 

    if (is_null()) 

    {

      return false; //null period isn't after

    }

    

    return t < begin_;

  }



  //! True if all of the period is prior to the passed point or end <= t

  /* In the example below points 4 and 5 return true.

   *@code

   *     [---------])

   * ^   ^    ^     ^   ^

   * 1   2    3     4   5

   * 

   *@endcode

   */

  template<class point_rep, class duration_rep>

  inline

  bool 

  period<point_rep,duration_rep>::is_before(const point_rep& t) const 

  { 

    if (is_null()) 

    {

      return false;  //null period isn't before anything

    }

    

    return last_ < t;

  }





  //! True if the periods overlap in any way

  /* In the example below p1 intersects with p2, p4, and p6.

   *@code

   *       [---p1---)

   *             [---p2---)

   *                [---p3---) 

   *  [---p4---) 

   * [-p5-) 

   *         [-p6-) 

   *@endcode

   */

  template<class point_rep, class duration_rep>

  inline

  bool period<point_rep,duration_rep>::intersects(const period<point_rep,duration_rep>& other) const 

  { 

    return ( contains(other.begin_) ||

             other.contains(begin_) ||

             ((other.begin_ < begin_) && (other.last_ >= begin_)));

  }



  //! Returns the period of intersection or invalid range no intersection

  template<class point_rep, class duration_rep>

  inline

  period<point_rep,duration_rep>

  period<point_rep,duration_rep>::intersection(const period<point_rep,duration_rep>& other) const 

  {

    if (begin_ > other.begin_) {

      if (last_ <= other.last_) { //case2

        return *this;  

      }

      //case 1

      return period<point_rep,duration_rep>(begin_, other.end());

    }

    else {

      if (last_ <= other.last_) { //case3

        return period<point_rep,duration_rep>(other.begin_, this->end());

      }

      //case4

      return other;

    }

    //unreachable

  }



  //! Returns the union of intersecting periods -- or null period

  /*! 

   */

  template<class point_rep, class duration_rep>

  inline

  period<point_rep,duration_rep>

  period<point_rep,duration_rep>::merge(const period<point_rep,duration_rep>& other) const 

  {

    if (this->intersects(other)) {      

      if (begin_ < other.begin_) {

        return period<point_rep,duration_rep>(begin_, last_ > other.last_ ? this->end() : other.end());

      }

      

      return period<point_rep,duration_rep>(other.begin_, last_ > other.last_ ? this->end() : other.end());

      

    }

    return period<point_rep,duration_rep>(begin_,begin_); // no intersect return null

  }



  //! Combine two periods with earliest start and latest end.

  /*! Combines two periods and any gap between them such that 

   *  start = min(p1.start, p2.start)

   *  end   = max(p1.end  , p2.end)

   *@code

   *        [---p1---)

   *                       [---p2---)

   * result:

   *        [-----------p3----------) 

   *@endcode

   */

  template<class point_rep, class duration_rep>

  inline

  period<point_rep,duration_rep>

  period<point_rep,duration_rep>::span(const period<point_rep,duration_rep>& other) const 

  {

    point_rep start((begin_ < other.begin_) ? begin() : other.begin());

    point_rep newend((last_  < other.last_)  ? other.end() : this->end());

    return period<point_rep,duration_rep>(start, newend);

  }





} } //namespace date_time







#endif

