/////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga  2007-2012
//
// Distributed under 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)
//
// See http://www.boost.org/libs/intrusive for documentation.
//
/////////////////////////////////////////////////////////////////////////////
// The implementation of splay trees is based on the article and code published
// in C++ Users Journal "Implementing Splay Trees in C++" (September 1, 2005).
//
// The code has been modified and (supposely) improved by Ion Gaztanaga.
// Here is the header of the file used as base code:
//
//  splay_tree.h -- implementation of a STL compatible splay tree.
//
//  Copyright (c) 2004 Ralf Mattethat
//
//  Permission to copy, use, modify, sell and distribute this software
//  is granted provided this copyright notice appears in all copies.
//  This software is provided "as is" without express or implied
//  warranty, and with no claim as to its suitability for any purpose.
//
//  Please send questions, comments, complaints, performance data, etc to
//  ralf.mattethat@teknologisk.dk
//
//  Requirements for element type
//  * must be copy-constructible
//  * destructor must not throw exception
//
//    Methods marked with note A only throws an exception if the evaluation of the
//    predicate throws an exception. If an exception is thrown the call has no
//    effect on the containers state
//
//    Methods marked with note B only throws an exception if the coppy constructor
//    or assignment operator of the predicate throws an exception. If an exception
//    is thrown the call has no effect on the containers state
//
//    iterators are only invalidated, if the element pointed to by the iterator
//    is deleted. The same goes for element references
//

#ifndef BOOST_INTRUSIVE_SPLAYTREE_ALGORITHMS_HPP
#define BOOST_INTRUSIVE_SPLAYTREE_ALGORITHMS_HPP

#include <boost/intrusive/detail/config_begin.hpp>
#include <boost/intrusive/detail/assert.hpp>
#include <boost/intrusive/intrusive_fwd.hpp>
#include <boost/intrusive/pointer_traits.hpp>
#include <cstddef>
#include <boost/intrusive/detail/utilities.hpp>
#include <boost/intrusive/detail/tree_algorithms.hpp>

namespace boost {
namespace intrusive {

/// @cond
namespace detail {

template<class NodeTraits>
struct splaydown_rollback
{
   typedef typename NodeTraits::node_ptr node_ptr;
   splaydown_rollback( const node_ptr *pcur_subtree, const node_ptr & header
                     , const node_ptr & leftmost           , const node_ptr & rightmost)
      : pcur_subtree_(pcur_subtree)  , header_(header)
      , leftmost_(leftmost)   , rightmost_(rightmost)
   {}

   void release()
   {  pcur_subtree_ = 0;  }

   ~splaydown_rollback()
   {
      if(pcur_subtree_){
         //Exception can only be thrown by comp, but
         //tree invariants still hold. *pcur_subtree is the current root
         //so link it to the header.
         NodeTraits::set_parent(*pcur_subtree_, header_);
         NodeTraits::set_parent(header_, *pcur_subtree_);
         //Recover leftmost/rightmost pointers
         NodeTraits::set_left (header_, leftmost_);
         NodeTraits::set_right(header_, rightmost_);
      }
   }
   const node_ptr *pcur_subtree_;
   node_ptr header_, leftmost_, rightmost_;
};

}  //namespace detail {
/// @endcond

//!   A splay tree is an implementation of a binary search tree. The tree is
//!   self balancing using the splay algorithm as described in
//!
//!      "Self-Adjusting Binary Search Trees
//!      by Daniel Dominic Sleator and Robert Endre Tarjan
//!      AT&T Bell Laboratories, Murray Hill, NJ
//!      Journal of the ACM, Vol 32, no 3, July 1985, pp 652-686

//! splaytree_algorithms is configured with a NodeTraits class, which encapsulates the
//! information about the node to be manipulated. NodeTraits must support the
//! following interface:
//!
//! Typedefs:
//!
//! <tt>node</tt>: The type of the node that forms the circular list
//!
//! <tt>node_ptr</tt>: A pointer to a node
//!
//! <tt>const_node_ptr</tt>: A pointer to a const node
//!
//! Static functions:
//!
//! <tt>static node_ptr get_parent(const_node_ptr n);</tt>
//!
//! <tt>static void set_parent(node_ptr n, node_ptr parent);</tt>
//!
//! <tt>static node_ptr get_left(const_node_ptr n);</tt>
//!
//! <tt>static void set_left(node_ptr n, node_ptr left);</tt>
//!
//! <tt>static node_ptr get_right(const_node_ptr n);</tt>
//!
//! <tt>static void set_right(node_ptr n, node_ptr right);</tt>
template<class NodeTraits>
class splaytree_algorithms
{
   /// @cond
   private:
   typedef detail::tree_algorithms<NodeTraits>  tree_algorithms;
   /// @endcond

   public:
   typedef typename NodeTraits::node            node;
   typedef NodeTraits                           node_traits;
   typedef typename NodeTraits::node_ptr        node_ptr;
   typedef typename NodeTraits::const_node_ptr  const_node_ptr;

   //! This type is the information that will be
   //! filled by insert_unique_check
   typedef typename tree_algorithms::insert_commit_data insert_commit_data;

   /// @cond
   private:
   static node_ptr uncast(const const_node_ptr & ptr)
   {  return pointer_traits<node_ptr>::const_cast_from(ptr);  }
   /// @endcond

   public:
   static node_ptr begin_node(const const_node_ptr & header)
   {  return tree_algorithms::begin_node(header);   }

   static node_ptr end_node(const const_node_ptr & header)
   {  return tree_algorithms::end_node(header);   }

   //! Requires: node is a node of the tree or an node initialized
   //!   by init(...).
   //!
   //! Effects: Returns true if the node is initialized by init().
   //!
   //! Complexity: Constant time.
   //!
   //! Throws: Nothing.
   static bool unique(const const_node_ptr & node)
   {  return tree_algorithms::unique(node);  }

   static void unlink(const node_ptr & node)
   {  tree_algorithms::unlink(node);   }

   //! Requires: node1 and node2 can't be header nodes
   //!  of two trees.
   //!
   //! Effects: Swaps two nodes. After the function node1 will be inserted
   //!   in the position node2 before the function. node2 will be inserted in the
   //!   position node1 had before the function.
   //!
   //! Complexity: Logarithmic.
   //!
   //! Throws: Nothing.
   //!
   //! Note: This function will break container ordering invariants if
   //!   node1 and node2 are not equivalent according to the ordering rules.
   //!
   //!Experimental function
   static void swap_nodes(const node_ptr & node1, const node_ptr & node2)
   {
      if(node1 == node2)
         return;

      node_ptr header1(tree_algorithms::get_header(node1)), header2(tree_algorithms::get_header(node2));
      swap_nodes(node1, header1, node2, header2);
   }

   //! Requires: node1 and node2 can't be header nodes
   //!  of two trees with header header1 and header2.
   //!
   //! Effects: Swaps two nodes. After the function node1 will be inserted
   //!   in the position node2 before the function. node2 will be inserted in the
   //!   position node1 had before the function.
   //!
   //! Complexity: Constant.
   //!
   //! Throws: Nothing.
   //!
   //! Note: This function will break container ordering invariants if
   //!   node1 and node2 are not equivalent according to the ordering rules.
   //!
   //!Experimental function
   static void swap_nodes(const node_ptr & node1, const node_ptr & header1, const node_ptr & node2, const node_ptr & header2)
   {  tree_algorithms::swap_nodes(node1, header1, node2, header2);   }

   //! Requires: node_to_be_replaced must be inserted in a tree
   //!   and new_node must not be inserted in a tree.
   //!
   //! Effects: Replaces node_to_be_replaced in its position in the
   //!   tree with new_node. The tree does not need to be rebalanced
   //!
   //! Complexity: Logarithmic.
   //!
   //! Throws: Nothing.
   //!
   //! Note: This function will break container ordering invariants if
   //!   new_node is not equivalent to node_to_be_replaced according to the
   //!   ordering rules. This function is faster than erasing and inserting
   //!   the node, since no rebalancing and comparison is needed.
   //!
   //!Experimental function
   static void replace_node(const node_ptr & node_to_be_replaced, const node_ptr & new_node)
   {
      if(node_to_be_replaced == new_node)
         return;
      replace_node(node_to_be_replaced, tree_algorithms::get_header(node_to_be_replaced), new_node);
   }

   //! Requires: node_to_be_replaced must be inserted in a tree
   //!   with header "header" and new_node must not be inserted in a tree.
   //!
   //! Effects: Replaces node_to_be_replaced in its position in the
   //!   tree with new_node. The tree does not need to be rebalanced
   //!
   //! Complexity: Constant.
   //!
   //! Throws: Nothing.
   //!
   //! Note: This function will break container ordering invariants if
   //!   new_node is not equivalent to node_to_be_replaced according to the
   //!   ordering rules. This function is faster than erasing and inserting
   //!   the node, since no rebalancing or comparison is needed.
   //!
   //!Experimental function
   static void replace_node(const node_ptr & node_to_be_replaced, const node_ptr & header, const node_ptr & new_node)
   {  tree_algorithms::replace_node(node_to_be_replaced, header, new_node);   }

   //! Requires: p is a node from the tree except the header.
   //!
   //! Effects: Returns the next node of the tree.
   //!
   //! Complexity: Average constant time.
   //!
   //! Throws: Nothing.
   static node_ptr next_node(const node_ptr & p)
   {  return tree_algorithms::next_node(p); }

   //! Requires: p is a node from the tree except the leftmost node.
   //!
   //! Effects: Returns the previous node of the tree.
   //!
   //! Complexity: Average constant time.
   //!
   //! Throws: Nothing.
   static node_ptr prev_node(const node_ptr & p)
   {  return tree_algorithms::prev_node(p); }

   //! Requires: node must not be part of any tree.
   //!
   //! Effects: After the function unique(node) == true.
   //!
   //! Complexity: Constant.
   //!
   //! Throws: Nothing.
   //!
   //! Nodes: If node is inserted in a tree, this function corrupts the tree.
   static void init(const node_ptr & node)
   {  tree_algorithms::init(node);  }

   //! Requires: node must not be part of any tree.
   //!
   //! Effects: Initializes the header to represent an empty tree.
   //!   unique(header) == true.
   //!
   //! Complexity: Constant.
   //!
   //! Throws: Nothing.
   //!
   //! Nodes: If node is inserted in a tree, this function corrupts the tree.
   static void init_header(const node_ptr & header)
   {  tree_algorithms::init_header(header);  }

   //! Requires: "disposer" must be an object function
   //!   taking a node_ptr parameter and shouldn't throw.
   //!
   //! Effects: Empties the target tree calling
   //!   <tt>void disposer::operator()(const node_ptr &)</tt> for every node of the tree
   //!    except the header.
   //!
   //! Complexity: Linear to the number of element of the source tree plus the.
   //!   number of elements of tree target tree when calling this function.
   //!
   //! Throws: If cloner functor throws. If this happens target nodes are disposed.
   template<class Disposer>
   static void clear_and_dispose(const node_ptr & header, Disposer disposer)
   {  tree_algorithms::clear_and_dispose(header, disposer); }

   //! Requires: node is a node of the tree but it's not the header.
   //!
   //! Effects: Returns the number of nodes of the subtree.
   //!
   //! Complexity: Linear time.
   //!
   //! Throws: Nothing.
   static std::size_t count(const const_node_ptr & node)
   {  return tree_algorithms::count(node);   }

   //! Requires: header is the header node of the tree.
   //!
   //! Effects: Returns the number of nodes above the header.
   //!
   //! Complexity: Linear time.
   //!
   //! Throws: Nothing.
   static std::size_t size(const const_node_ptr & header)
   {  return tree_algorithms::size(header);   }

   //! Requires: header1 and header2 must be the header nodes
   //!  of two trees.
   //!
   //! Effects: Swaps two trees. After the function header1 will contain
   //!   links to the second tree and header2 will have links to the first tree.
   //!
   //! Complexity: Constant.
   //!
   //! Throws: Nothing.
   static void swap_tree(const node_ptr & header1, const node_ptr & header2)
   {  return tree_algorithms::swap_tree(header1, header2);  }

   //! Requires: "header" must be the header node of a tree.
   //!   "commit_data" must have been obtained from a previous call to
   //!   "insert_unique_check". No objects should have been inserted or erased
   //!   from the set between the "insert_unique_check" that filled "commit_data"
   //!   and the call to "insert_commit".
   //!
   //!
   //! Effects: Inserts new_node in the set using the information obtained
   //!   from the "commit_data" that a previous "insert_check" filled.
   //!
   //! Complexity: Constant time.
   //!
   //! Throws: Nothing.
   //!
   //! Notes: This function has only sense if a "insert_unique_check" has been
   //!   previously executed to fill "commit_data". No value should be inserted or
   //!   erased between the "insert_check" and "insert_commit" calls.
   static void insert_unique_commit
      (const node_ptr & header, const node_ptr & new_value, const insert_commit_data &commit_data)
   {  tree_algorithms::insert_unique_commit(header, new_value, commit_data);  }

   //! Requires: "header" must be the header node of a tree.
   //!   KeyNodePtrCompare is a function object that induces a strict weak
   //!   ordering compatible with the strict weak ordering used to create the
   //!   the tree. NodePtrCompare compares KeyType with a node_ptr.
   //!
   //! Effects: Checks if there is an equivalent node to "key" in the
   //!   tree according to "comp" and obtains the needed information to realize
   //!   a constant-time node insertion if there is no equivalent node.
   //!
   //! Returns: If there is an equivalent value
   //!   returns a pair containing a node_ptr to the already present node
   //!   and false. If there is not equivalent key can be inserted returns true
   //!   in the returned pair's boolean and fills "commit_data" that is meant to
   //!   be used with the "insert_commit" function to achieve a constant-time
   //!   insertion function.
   //!
   //! Complexity: Average complexity is at most logarithmic.
   //!
   //! Throws: If "comp" throws.
   //!
   //! Notes: This function is used to improve performance when constructing
   //!   a node is expensive and the user does not want to have two equivalent nodes
   //!   in the tree: if there is an equivalent value
   //!   the constructed object must be discarded. Many times, the part of the
   //!   node that is used to impose the order is much cheaper to construct
   //!   than the node and this function offers the possibility to use that part
   //!   to check if the insertion will be successful.
   //!
   //!   If the check is successful, the user can construct the node and use
   //!   "insert_commit" to insert the node in constant-time. This gives a total
   //!   logarithmic complexity to the insertion: check(O(log(N)) + commit(O(1)).
   //!
   //!   "commit_data" remains valid for a subsequent "insert_unique_commit" only
   //!   if no more objects are inserted or erased from the set.
   template<class KeyType, class KeyNodePtrCompare>
   static std::pair<node_ptr, bool> insert_unique_check
      (const node_ptr & header, const KeyType &key
      ,KeyNodePtrCompare comp, insert_commit_data &commit_data)
   {
      splay_down(header, key, comp);
      return tree_algorithms::insert_unique_check(header, key, comp, commit_data);
   }

   template<class KeyType, class KeyNodePtrCompare>
   static std::pair<node_ptr, bool> insert_unique_check
      (const node_ptr & header, const node_ptr &hint, const KeyType &key
      ,KeyNodePtrCompare comp, insert_commit_data &commit_data)
   {
      splay_down(header, key, comp);
      return tree_algorithms::insert_unique_check(header, hint, key, comp, commit_data);
   }

   static bool is_header(const const_node_ptr & p)
   {  return tree_algorithms::is_header(p);  }

   //! Requires: "header" must be the header node of a tree.
   //!   KeyNodePtrCompare is a function object that induces a strict weak
   //!   ordering compatible with the strict weak ordering used to create the
   //!   the tree. KeyNodePtrCompare can compare KeyType with tree's node_ptrs.
   //!
   //! Effects: Returns an node_ptr to the element that is equivalent to
   //!   "key" according to "comp" or "header" if that element does not exist.
   //!
   //! Complexity: Logarithmic.
   //!
   //! Throws: If "comp" throws.
   template<class KeyType, class KeyNodePtrCompare>
   static node_ptr find
      (const const_node_ptr & header, const KeyType &key, KeyNodePtrCompare comp, bool splay = true)
   {
      if(splay)
         splay_down(uncast(header), key, comp);
      node_ptr end = uncast(header);
      node_ptr y = lower_bound(header, key, comp, false);
      node_ptr r = (y == end || comp(key, y)) ? end : y;
      return r;
   }

   //! Requires: "header" must be the header node of a tree.
   //!   KeyNodePtrCompare is a function object that induces a strict weak
   //!   ordering compatible with the strict weak ordering used to create the
   //!   the tree. KeyNodePtrCompare can compare KeyType with tree's node_ptrs.
   //!
   //! Effects: Returns an a pair of node_ptr delimiting a range containing
   //!   all elements that are equivalent to "key" according to "comp" or an
   //!   empty range that indicates the position where those elements would be
   //!   if they there are no equivalent elements.
   //!
   //! Complexity: Logarithmic.
   //!
   //! Throws: If "comp" throws.
   template<class KeyType, class KeyNodePtrCompare>
   static std::pair<node_ptr, node_ptr> equal_range
      (const const_node_ptr & header, const KeyType &key, KeyNodePtrCompare comp, bool splay = true)
   {
      //if(splay)
         //splay_down(uncast(header), key, comp);
      std::pair<node_ptr, node_ptr> ret =
         tree_algorithms::equal_range(header, key, comp);

      if(splay)
         splay_up(ret.first, uncast(header));
      return ret;
   }

   //! Requires: "header" must be the header node of a tree.
   //!   KeyNodePtrCompare is a function object that induces a strict weak
   //!   ordering compatible with the strict weak ordering used to create the
   //!   the tree. KeyNodePtrCompare can compare KeyType with tree's node_ptrs.
   //!   'lower_key' must not be greater than 'upper_key' according to 'comp'. If
   //!   'lower_key' == 'upper_key', ('left_closed' || 'right_closed') must be false.
   //!
   //! Effects: Returns an a pair with the following criteria:
   //!
   //!   first = lower_bound(lower_key) if left_closed, upper_bound(lower_key) otherwise
   //!
   //!   second = upper_bound(upper_key) if right_closed, lower_bound(upper_key) otherwise
   //!
   //! Complexity: Logarithmic.
   //!
   //! Throws: If "comp" throws.
   //!
   //! Note: This function can be more efficient than calling upper_bound
   //!   and lower_bound for lower_key and upper_key.
   template<class KeyType, class KeyNodePtrCompare>
   static std::pair<node_ptr, node_ptr> bounded_range
      (const const_node_ptr & header, const KeyType &lower_key, const KeyType &upper_key, KeyNodePtrCompare comp
      , bool left_closed, bool right_closed, bool splay = true)
   {
      std::pair<node_ptr, node_ptr> ret =
         tree_algorithms::bounded_range(header, lower_key, upper_key, comp, left_closed, right_closed);

      if(splay)
         splay_up(ret.first, uncast(header));
      return ret;
   }

   //! Requires: "header" must be the header node of a tree.
   //!   KeyNodePtrCompare is a function object that induces a strict weak
   //!   ordering compatible with the strict weak ordering used to create the
   //!   the tree. KeyNodePtrCompare can compare KeyType with tree's node_ptrs.
   //!
   //! Effects: Returns an node_ptr to the first element that is
   //!   not less than "key" according to "comp" or "header" if that element does
   //!   not exist.
   //!
   //! Complexity: Logarithmic.
   //!
   //! Throws: If "comp" throws.
   template<class KeyType, class KeyNodePtrCompare>
   static node_ptr lower_bound
      (const const_node_ptr & header, const KeyType &key, KeyNodePtrCompare comp, bool splay = true)
   {
      //if(splay)
         //splay_down(uncast(header), key, comp);
      node_ptr y = tree_algorithms::lower_bound(header, key, comp);
      if(splay)
         splay_up(y, uncast(header));
      return y;
   }

   //! Requires: "header" must be the header node of a tree.
   //!   KeyNodePtrCompare is a function object that induces a strict weak
   //!   ordering compatible with the strict weak ordering used to create the
   //!   the tree. KeyNodePtrCompare can compare KeyType with tree's node_ptrs.
   //!
   //! Effects: Returns an node_ptr to the first element that is greater
   //!   than "key" according to "comp" or "header" if that element does not exist.
   //!
   //! Complexity: Logarithmic.
   //!
   //! Throws: If "comp" throws.
   template<class KeyType, class KeyNodePtrCompare>
   static node_ptr upper_bound
      (const const_node_ptr & header, const KeyType &key, KeyNodePtrCompare comp, bool splay = true)
   {
      //if(splay)
         //splay_down(uncast(header), key, comp);
      node_ptr y = tree_algorithms::upper_bound(header, key, comp);
      if(splay)
         splay_up(y, uncast(header));
      return y;
   }

   //! Requires: "header" must be the header node of a tree.
   //!   NodePtrCompare is a function object that induces a strict weak
   //!   ordering compatible with the strict weak ordering used to create the
   //!   the tree. NodePtrCompare compares two node_ptrs. "hint" is node from
   //!   the "header"'s tree.
   //!
   //! Effects: Inserts new_node into the tree, using "hint" as a hint to
   //!   where it will be inserted. If "hint" is the upper_bound
   //!   the insertion takes constant time (two comparisons in the worst case).
   //!
   //! Complexity: Logarithmic in general, but it is amortized
   //!   constant time if new_node is inserted immediately before "hint".
   //!
   //! Throws: If "comp" throws.
   template<class NodePtrCompare>
   static node_ptr insert_equal
      (const node_ptr & header, const node_ptr & hint, const node_ptr & new_node, NodePtrCompare comp)
   {
      splay_down(header, new_node, comp);
      return tree_algorithms::insert_equal(header, hint, new_node, comp);
   }

   //! Requires: "header" must be the header node of a tree.
   //!   "pos" must be a valid iterator or header (end) node.
   //!   "pos" must be an iterator pointing to the successor to "new_node"
   //!   once inserted according to the order of already inserted nodes. This function does not
   //!   check "pos" and this precondition must be guaranteed by the caller.
   //!
   //! Effects: Inserts new_node into the tree before "pos".
   //!
   //! Complexity: Constant-time.
   //!
   //! Throws: Nothing.
   //!
   //! Note: If "pos" is not the successor of the newly inserted "new_node"
   //! tree invariants might be broken.
   static node_ptr insert_before
      (const node_ptr & header, const node_ptr & pos, const node_ptr & new_node)
   {
      tree_algorithms::insert_before(header, pos, new_node);
      splay_up(new_node, header);
      return new_node;
   }

   //! Requires: "header" must be the header node of a tree.
   //!   "new_node" must be, according to the used ordering no less than the
   //!   greatest inserted key.
   //!
   //! Effects: Inserts new_node into the tree before "pos".
   //!
   //! Complexity: Constant-time.
   //!
   //! Throws: Nothing.
   //!
   //! Note: If "new_node" is less than the greatest inserted key
   //! tree invariants are broken. This function is slightly faster than
   //! using "insert_before".
   static void push_back(const node_ptr & header, const node_ptr & new_node)
   {
      tree_algorithms::push_back(header, new_node);
      splay_up(new_node, header);
   }

   //! Requires: "header" must be the header node of a tree.
   //!   "new_node" must be, according to the used ordering, no greater than the
   //!   lowest inserted key.
   //!
   //! Effects: Inserts new_node into the tree before "pos".
   //!
   //! Complexity: Constant-time.
   //!
   //! Throws: Nothing.
   //!
   //! Note: If "new_node" is greater than the lowest inserted key
   //! tree invariants are broken. This function is slightly faster than
   //! using "insert_before".
   static void push_front(const node_ptr & header, const node_ptr & new_node)
   {
      tree_algorithms::push_front(header, new_node);
      splay_up(new_node, header);
   }

   //! Requires: "header" must be the header node of a tree.
   //!   NodePtrCompare is a function object that induces a strict weak
   //!   ordering compatible with the strict weak ordering used to create the
   //!   the tree. NodePtrCompare compares two node_ptrs.
   //!
   //! Effects: Inserts new_node into the tree before the upper bound
   //!   according to "comp".
   //!
   //! Complexity: Average complexity for insert element is at
   //!   most logarithmic.
   //!
   //! Throws: If "comp" throws.
   template<class NodePtrCompare>
   static node_ptr insert_equal_upper_bound
      (const node_ptr & header, const node_ptr & new_node, NodePtrCompare comp)
   {
      splay_down(header, new_node, comp);
      return tree_algorithms::insert_equal_upper_bound(header, new_node, comp);
   }

   //! Requires: "header" must be the header node of a tree.
   //!   NodePtrCompare is a function object that induces a strict weak
   //!   ordering compatible with the strict weak ordering used to create the
   //!   the tree. NodePtrCompare compares two node_ptrs.
   //!
   //! Effects: Inserts new_node into the tree before the lower bound
   //!   according to "comp".
   //!
   //! Complexity: Average complexity for insert element is at
   //!   most logarithmic.
   //!
   //! Throws: If "comp" throws.
   template<class NodePtrCompare>
   static node_ptr insert_equal_lower_bound
      (const node_ptr & header, const node_ptr & new_node, NodePtrCompare comp)
   {
      splay_down(header, new_node, comp);
      return tree_algorithms::insert_equal_lower_bound(header, new_node, comp);
   }

   //! Requires: "cloner" must be a function
   //!   object taking a node_ptr and returning a new cloned node of it. "disposer" must
   //!   take a node_ptr and shouldn't throw.
   //!
   //! Effects: First empties target tree calling
   //!   <tt>void disposer::operator()(const node_ptr &)</tt> for every node of the tree
   //!    except the header.
   //!
   //!   Then, duplicates the entire tree pointed by "source_header" cloning each
   //!   source node with <tt>node_ptr Cloner::operator()(const node_ptr &)</tt> to obtain
   //!   the nodes of the target tree. If "cloner" throws, the cloned target nodes
   //!   are disposed using <tt>void disposer(const node_ptr &)</tt>.
   //!
   //! Complexity: Linear to the number of element of the source tree plus the.
   //!   number of elements of tree target tree when calling this function.
   //!
   //! Throws: If cloner functor throws. If this happens target nodes are disposed.
   template <class Cloner, class Disposer>
   static void clone
      (const const_node_ptr & source_header, const node_ptr & target_header, Cloner cloner, Disposer disposer)
   {  tree_algorithms::clone(source_header, target_header, cloner, disposer);   }

   // delete node                        | complexity : constant        | exception : nothrow
   static void erase(const node_ptr & header, const node_ptr & z, bool splay = true)
   {
//      node_base* n = t->right;
//      if( t->left != node_ptr() ){
//         node_base* l = t->previous();
//         splay_up( l , t );
//         n = t->left;
//         n->right = t->right;
//         if( n->right != node_ptr() )
//            n->right->parent = n;
//      }
//
//      if( n != node_ptr() )
//         n->parent = t->parent;
//
//      if( t->parent->left == t )
//         t->parent->left = n;
//      else // must be ( t->parent->right == t )
//         t->parent->right = n;
//
//      if( data_->parent == t )
//         data_->parent = find_leftmost();
         //posibility 1
      if(splay && NodeTraits::get_left(z)){
         splay_up(prev_node(z), header);
      }
      /*
      //possibility 2
      if(splay && NodeTraits::get_left(z) != node_ptr() ){
         node_ptr l = NodeTraits::get_left(z);
         splay_up(l, header);
      }*//*
      if(splay && NodeTraits::get_left(z) != node_ptr() ){
         node_ptr l = prev_node(z);
         splay_up_impl(l, z);
      }*/
      /*
      //possibility 4
      if(splay){
         splay_up(z, header);
      }*/

      //if(splay)
         //splay_up(z, header);
      tree_algorithms::erase(header, z);
   }

   // bottom-up splay, use data_ as parent for n    | complexity : logarithmic    | exception : nothrow
   static void splay_up(const node_ptr & node, const node_ptr & header)
   {
      // If (node == header) do a splay for the right most node instead
      // this is to boost performance of equal_range/count on equivalent containers in the case
      // where there are many equal elements at the end
      node_ptr n((node == header) ? NodeTraits::get_right(header) : node);
      node_ptr t(header);

      if( n == t ) return;

      for( ;; ){
         node_ptr p(NodeTraits::get_parent(n));
         node_ptr g(NodeTraits::get_parent(p));

         if( p == t )   break;

         if( g == t ){
            // zig
            rotate(n);
         }
         else if ((NodeTraits::get_left(p) == n && NodeTraits::get_left(g) == p)    ||
                  (NodeTraits::get_right(p) == n && NodeTraits::get_right(g) == p)  ){
            // zig-zig
            rotate(p);
            rotate(n);
         }
         else{
            // zig-zag
            rotate(n);
            rotate(n);
         }
      }
   }

   // top-down splay | complexity : logarithmic    | exception : strong, note A
   template<class KeyType, class KeyNodePtrCompare>
   static node_ptr splay_down(const node_ptr & header, const KeyType &key, KeyNodePtrCompare comp)
   {
      if(!NodeTraits::get_parent(header))
         return header;
      //Most splay tree implementations use a dummy/null node to implement.
      //this function. This has some problems for a generic library like Intrusive:
      //
      // * The node might not have a default constructor.
      // * The default constructor could throw.
      //
      //We already have a header node. Leftmost and rightmost nodes of the tree
      //are not changed when splaying (because the invariants of the tree don't
      //change) We can back up them, use the header as the null node and
      //reassign old values after the function has been completed.
      node_ptr t = NodeTraits::get_parent(header);
      //Check if tree has a single node
      if(!NodeTraits::get_left(t) && !NodeTraits::get_right(t))
         return t;
      //Backup leftmost/rightmost
      node_ptr leftmost (NodeTraits::get_left(header));
      node_ptr rightmost(NodeTraits::get_right(header));
      {
         //Anti-exception rollback, recovers the original header node if an exception is thrown.
         detail::splaydown_rollback<NodeTraits> rollback(&t, header, leftmost, rightmost);
         node_ptr null_node = header;
         node_ptr l = null_node;
         node_ptr r = null_node;

         for( ;; ){
            if(comp(key, t)){
               if(NodeTraits::get_left(t) == node_ptr() )
                  break;
               if(comp(key, NodeTraits::get_left(t))){
                  t = tree_algorithms::rotate_right(t);

                  if(NodeTraits::get_left(t) == node_ptr())
                     break;
                  link_right(t, r);
               }
               else if(comp(NodeTraits::get_left(t), key)){
                  link_right(t, r);

                  if(NodeTraits::get_right(t) == node_ptr() )
                     break;
                  link_left(t, l);
               }
               else{
                  link_right(t, r);
               }
            }
            else if(comp(t, key)){
               if(NodeTraits::get_right(t) == node_ptr() )
                  break;

               if(comp(NodeTraits::get_right(t), key)){
                     t = tree_algorithms::rotate_left( t );

                     if(NodeTraits::get_right(t) == node_ptr() )
                        break;
                     link_left(t, l);
               }
               else if(comp(key, NodeTraits::get_right(t))){
                  link_left(t, l);

                  if(NodeTraits::get_left(t) == node_ptr())
                     break;

                  link_right(t, r);
               }
               else{
                  link_left(t, l);
               }
            }
            else{
               break;
            }
         }

         assemble(t, l, r, null_node);
         rollback.release();
      }

      //Now recover the original header except for the
      //splayed root node.
      //t is the current root
      NodeTraits::set_parent(header, t);
      NodeTraits::set_parent(t, header);
      //Recover leftmost/rightmost pointers
      NodeTraits::set_left (header, leftmost);
      NodeTraits::set_right(header, rightmost);
      return t;
   }

   //! Requires: header must be the header of a tree.
   //!
   //! Effects: Rebalances the tree.
   //!
   //! Throws: Nothing.
   //!
   //! Complexity: Linear.
   static void rebalance(const node_ptr & header)
   {  tree_algorithms::rebalance(header); }

   //! Requires: old_root is a node of a tree.
   //!
   //! Effects: Rebalances the subtree rooted at old_root.
   //!
   //! Returns: The new root of the subtree.
   //!
   //! Throws: Nothing.
   //!
   //! Complexity: Linear.
   static node_ptr rebalance_subtree(const node_ptr & old_root)
   {  return tree_algorithms::rebalance_subtree(old_root); }

   //! Requires: "n" must be a node inserted in a tree.
   //!
   //! Effects: Returns a pointer to the header node of the tree.
   //!
   //! Complexity: Logarithmic.
   //!
   //! Throws: Nothing.
   static node_ptr get_header(const node_ptr & n)
   {  return tree_algorithms::get_header(n);   }

   private:

   /// @cond

   // assemble the three sub-trees into new tree pointed to by t    | complexity : constant        | exception : nothrow
   static void assemble(const node_ptr &t, const node_ptr & l, const node_ptr & r, const const_node_ptr & null_node )
   {
      NodeTraits::set_right(l, NodeTraits::get_left(t));
      NodeTraits::set_left(r, NodeTraits::get_right(t));

      if(NodeTraits::get_right(l) != node_ptr()){
         NodeTraits::set_parent(NodeTraits::get_right(l), l);
      }

      if(NodeTraits::get_left(r) != node_ptr()){
         NodeTraits::set_parent(NodeTraits::get_left(r), r);
      }

      NodeTraits::set_left (t, NodeTraits::get_right(null_node));
      NodeTraits::set_right(t, NodeTraits::get_left(null_node));

      if( NodeTraits::get_left(t) != node_ptr() ){
         NodeTraits::set_parent(NodeTraits::get_left(t), t);
      }

      if( NodeTraits::get_right(t) ){
         NodeTraits::set_parent(NodeTraits::get_right(t), t);
      }
   }

   // break link to left child node and attach it to left tree pointed to by l   | complexity : constant | exception : nothrow
   static void link_left(node_ptr & t, node_ptr & l)
   {
      NodeTraits::set_right(l, t);
      NodeTraits::set_parent(t, l);
      l = t;
      t = NodeTraits::get_right(t);
   }

   // break link to right child node and attach it to right tree pointed to by r | complexity : constant | exception : nothrow
   static void link_right(node_ptr & t, node_ptr & r)
   {
      NodeTraits::set_left(r, t);
      NodeTraits::set_parent(t, r);
      r = t;
      t = NodeTraits::get_left(t);
   }

   // rotate n with its parent                     | complexity : constant    | exception : nothrow
   static void rotate(const node_ptr & n)
   {
      node_ptr p = NodeTraits::get_parent(n);
      node_ptr g = NodeTraits::get_parent(p);
      //Test if g is header before breaking tree
      //invariants that would make is_header invalid
      bool g_is_header = is_header(g);

      if(NodeTraits::get_left(p) == n){
         NodeTraits::set_left(p, NodeTraits::get_right(n));
         if(NodeTraits::get_left(p) != node_ptr())
            NodeTraits::set_parent(NodeTraits::get_left(p), p);
         NodeTraits::set_right(n, p);
      }
      else{ // must be ( p->right == n )
         NodeTraits::set_right(p, NodeTraits::get_left(n));
         if(NodeTraits::get_right(p) != node_ptr())
            NodeTraits::set_parent(NodeTraits::get_right(p), p);
         NodeTraits::set_left(n, p);
      }

      NodeTraits::set_parent(p, n);
      NodeTraits::set_parent(n, g);

      if(g_is_header){
         if(NodeTraits::get_parent(g) == p)
            NodeTraits::set_parent(g, n);
         else{//must be ( g->right == p )
            BOOST_INTRUSIVE_INVARIANT_ASSERT(false);
            NodeTraits::set_right(g, n);
         }
      }
      else{
         if(NodeTraits::get_left(g) == p)
            NodeTraits::set_left(g, n);
         else  //must be ( g->right == p )
            NodeTraits::set_right(g, n);
      }
   }

   /// @endcond
};

} //namespace intrusive
} //namespace boost

#include <boost/intrusive/detail/config_end.hpp>

#endif //BOOST_INTRUSIVE_SPLAYTREE_ALGORITHMS_HPP


/*
 * Copyright (c) 2011, Mattia Penati <mattia.penati@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Politecnico di Milano nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef AMA_TENSOR_IEXP_IEXP_CONSTANT_HPP
#define AMA_TENSOR_IEXP_IEXP_CONSTANT_HPP 1

#include <ama/tensor/iexp/index_reorder.hpp>
#include <ama/tensor/iexp/iexp_base.hpp>
#include <boost/mpl/bool.hpp>

namespace ama
{
  namespace tensor_
  {

    /* forward declaration */
    template <
          typename TENSOR /* the tensor indixed */
        , typename CTLIST /* the controvariant list */
        , typename COLIST /* the covariant list */
        > class iexp_constant;

    /* specialization of iexp_traits */
    template <typename TENSOR, typename CTLIST, typename COLIST>
    struct iexp_traits < iexp_constant<TENSOR, CTLIST, COLIST> >
    {
      typedef typename TENSOR::value_type value_type;

      typedef typename TENSOR::dimension_type dimension_type;

      typedef CTLIST controvariant_list;
      typedef COLIST covariant_list;

      typedef ::boost::mpl::false_ is_assignable;
    };

    /* class declaration */
    template <typename TENSOR, typename CTLIST, typename COLIST>
    class iexp_constant:
      public iexp_base< iexp_constant<TENSOR, CTLIST, COLIST> >
    {
    protected:
      typedef iexp_base< iexp_constant<TENSOR, CTLIST, COLIST> > base_type;
      typedef iexp_constant<TENSOR, CTLIST, COLIST> derived_type;

    public:
      typedef typename base_type::value_type value_type;
      typedef typename base_type::index_list index_list;

    protected:
      typedef TENSOR tensor_type;

    public:
      /* constructor */
      explicit iexp_constant(tensor_type const & t): m_t(t) { }

    public:
      /* retrieve the value */
      template <typename IMAP>
      value_type at() const
      {
        /* reorder the indices */
        typedef typename index_reorder<IMAP, index_list>::type ilist;

        return m_t.template at<ilist>();
      }

    protected:
      tensor_type const & m_t;
    };

  }
}

#endif /* AMA_TENSOR_IEXP_IEXP_CONSTANT_HPP */


// Boost.Geometry

// Copyright (c) 2017-2018, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_ALGORITHMS_DENSIFY_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DENSIFY_HPP

#include <boost/geometry/algorithms/clear.hpp>
#include <boost/geometry/algorithms/detail/convert_point_to_point.hpp>
#include <boost/geometry/algorithms/not_implemented.hpp>
#include <boost/geometry/core/closure.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/exception.hpp>
#include <boost/geometry/core/point_type.hpp>
#include <boost/geometry/core/tag.hpp>
#include <boost/geometry/core/tags.hpp>
#include <boost/geometry/strategies/default_strategy.hpp>
#include <boost/geometry/strategies/densify.hpp>
#include <boost/geometry/util/condition.hpp>
#include <boost/geometry/util/range.hpp>

#include <boost/range/size.hpp>
#include <boost/range/value_type.hpp>

#include <boost/throw_exception.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace densify
{

template <typename Range>
struct push_back_policy
{
    typedef typename boost::range_value<Range>::type point_type;

    inline explicit push_back_policy(Range & rng)
        : m_rng(rng)
    {}

    inline void apply(point_type const& p)
    {
        range::push_back(m_rng, p);
    }

private:
    Range & m_rng;
};

template <typename Range, typename Point>
inline void convert_and_push_back(Range & range, Point const& p)
{
    typename boost::range_value<Range>::type p2;
    geometry::detail::conversion::convert_point_to_point(p, p2);
    range::push_back(range, p2);
}

template <bool AppendLastPoint = true>
struct densify_range
{
    template <typename FwdRng, typename MutRng, typename T, typename Strategy>
    static inline void apply(FwdRng const& rng, MutRng & rng_out,
                             T const& len, Strategy const& strategy)
    {
        typedef typename boost::range_iterator<FwdRng const>::type iterator_t;
        typedef typename boost::range_value<FwdRng>::type point_t;

        iterator_t it = boost::begin(rng);
        iterator_t end = boost::end(rng);

        if (it == end) // empty(rng)
        {
            return;
        }

        push_back_policy<MutRng> policy(rng_out);

        iterator_t prev = it;
        for ( ++it ; it != end ; prev = it++)
        {
            point_t const& p0 = *prev;
            point_t const& p1 = *it;

            convert_and_push_back(rng_out, p0);

            strategy.apply(p0, p1, policy, len);
        }

        if (BOOST_GEOMETRY_CONDITION(AppendLastPoint))
        {
            convert_and_push_back(rng_out, *prev); // back(rng)
        }
    }
};

template <bool IsClosed1, bool IsClosed2> // false, X
struct densify_ring
{
    template <typename Geometry, typename GeometryOut, typename T, typename Strategy>
    static inline void apply(Geometry const& ring, GeometryOut & ring_out,
                             T const& len, Strategy const& strategy)
    {
        geometry::detail::densify::densify_range<true>
            ::apply(ring, ring_out, len, strategy);

        if (boost::size(ring) <= 1)
            return;

        typedef typename point_type<Geometry>::type point_t;
        point_t const& p0 = range::back(ring);
        point_t const& p1 = range::front(ring);

        push_back_policy<GeometryOut> policy(ring_out);

        strategy.apply(p0, p1, policy, len);

        if (BOOST_GEOMETRY_CONDITION(IsClosed2))
        {
            convert_and_push_back(ring_out, p1);
        }
    }
};

template <>
struct densify_ring<true, true>
    : densify_range<true>
{};

template <>
struct densify_ring<true, false>
    : densify_range<false>
{};

}} // namespace detail::densify
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template
<
    typename Geometry,
    typename GeometryOut,
    typename Tag1 = typename tag<Geometry>::type,
    typename Tag2 = typename tag<GeometryOut>::type
>
struct densify
    : not_implemented<Tag1, Tag2>
{};

template <typename Geometry, typename GeometryOut>
struct densify<Geometry, GeometryOut, linestring_tag, linestring_tag>
    : geometry::detail::densify::densify_range<>
{};

template <typename Geometry, typename GeometryOut>
struct densify<Geometry, GeometryOut, multi_linestring_tag, multi_linestring_tag>
{
    template <typename T, typename Strategy>
    static void apply(Geometry const& mls, GeometryOut & mls_out,
                      T const& len, Strategy const& strategy)
    {
        std::size_t count = boost::size(mls);
        range::resize(mls_out, count);

        for (std::size_t i = 0 ; i < count ; ++i)
        {
            geometry::detail::densify::densify_range<>
                ::apply(range::at(mls, i), range::at(mls_out, i),
                        len, strategy);
        }
    }
};

template <typename Geometry, typename GeometryOut>
struct densify<Geometry, GeometryOut, ring_tag, ring_tag>
    : geometry::detail::densify::densify_ring
        <
            geometry::closure<Geometry>::value != geometry::open,
            geometry::closure<GeometryOut>::value != geometry::open
        >
{};

template <typename Geometry, typename GeometryOut>
struct densify<Geometry, GeometryOut, polygon_tag, polygon_tag>
{
    template <typename T, typename Strategy>
    static void apply(Geometry const& poly, GeometryOut & poly_out,
                      T const& len, Strategy const& strategy)
    {
        apply_ring(exterior_ring(poly), exterior_ring(poly_out),
                   len, strategy);

        std::size_t count = boost::size(interior_rings(poly));
        range::resize(interior_rings(poly_out), count);

        for (std::size_t i = 0 ; i < count ; ++i)
        {
            apply_ring(range::at(interior_rings(poly), i),
                       range::at(interior_rings(poly_out), i),
                       len, strategy);
        }
    }

    template <typename Ring, typename RingOut, typename T, typename Strategy>
    static void apply_ring(Ring const& ring, RingOut & ring_out,
                           T const& len, Strategy const& strategy)
    {
        densify<Ring, RingOut, ring_tag, ring_tag>
            ::apply(ring, ring_out, len, strategy);
    }
};

template <typename Geometry, typename GeometryOut>
struct densify<Geometry, GeometryOut, multi_polygon_tag, multi_polygon_tag>
{
    template <typename T, typename Strategy>
    static void apply(Geometry const& mpoly, GeometryOut & mpoly_out,
                      T const& len, Strategy const& strategy)
    {
        std::size_t count = boost::size(mpoly);
        range::resize(mpoly_out, count);

        for (std::size_t i = 0 ; i < count ; ++i)
        {
            apply_poly(range::at(mpoly, i),
                       range::at(mpoly_out, i),
                       len, strategy);
        }
    }

    template <typename Poly, typename PolyOut, typename T, typename Strategy>
    static void apply_poly(Poly const& poly, PolyOut & poly_out,
                           T const& len, Strategy const& strategy)
    {
        densify<Poly, PolyOut, polygon_tag, polygon_tag>::
            apply(poly, poly_out, len, strategy);
    }
};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

namespace resolve_strategy
{

struct densify
{
    template <typename Geometry, typename Distance, typename Strategy>
    static inline void apply(Geometry const& geometry,
                             Geometry& out,
                             Distance const& max_distance,
                             Strategy const& strategy)
    {
        dispatch::densify<Geometry, Geometry>
            ::apply(geometry, out, max_distance, strategy);
    }

    template <typename Geometry, typename Distance>
    static inline void apply(Geometry const& geometry,
                             Geometry& out,
                             Distance const& max_distance,
                             default_strategy)
    {
        typedef typename strategy::densify::services::default_strategy
            <
                typename cs_tag<Geometry>::type
            >::type strategy_type;

        /*BOOST_CONCEPT_ASSERT(
            (concepts::DensifyStrategy<strategy_type>)
        );*/

        apply(geometry, out, max_distance, strategy_type());
    }
};

} // namespace resolve_strategy

namespace resolve_variant {

template <typename Geometry>
struct densify
{
    template <typename Distance, typename Strategy>
    static inline void apply(Geometry const& geometry,
                             Geometry& out,
                             Distance const& max_distance,
                             Strategy const& strategy)
    {
        resolve_strategy::densify::apply(geometry, out, max_distance, strategy);
    }
};

template <BOOST_VARIANT_ENUM_PARAMS(typename T)>
struct densify<boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> >
{
    template <typename Distance, typename Strategy>
    struct visitor: boost::static_visitor<void>
    {
        Distance const& m_max_distance;
        Strategy const& m_strategy;

        visitor(Distance const& max_distance, Strategy const& strategy)
            : m_max_distance(max_distance)
            , m_strategy(strategy)
        {}

        template <typename Geometry>
        void operator()(Geometry const& geometry, Geometry& out) const
        {
            densify<Geometry>::apply(geometry, out, m_max_distance, m_strategy);
        }
    };

    template <typename Distance, typename Strategy>
    static inline void
    apply(boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> const& geometry,
          boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)>& out,
          Distance const& max_distance,
          Strategy const& strategy)
    {
        boost::apply_visitor(
            visitor<Distance, Strategy>(max_distance, strategy),
            geometry,
            out
        );
    }
};

} // namespace resolve_variant

/*!
\brief Densify a geometry using a specified strategy
\ingroup densify
\tparam Geometry \tparam_geometry
\tparam Distance A numerical distance measure
\tparam Strategy A type fulfilling a DensifyStrategy concept
\param geometry Input geometry, to be densified
\param out Output geometry, densified version of the input geometry
\param max_distance Distance threshold (in units depending on strategy)
\param strategy Densify strategy to be used for densification

\qbk{distinguish,with strategy}
\qbk{[include reference/algorithms/densify.qbk]}

\qbk{
[heading Available Strategies]
\* [link geometry.reference.strategies.strategy_densify_cartesian Cartesian]
\* [link geometry.reference.strategies.strategy_densify_spherical Spherical]
\* [link geometry.reference.strategies.strategy_densify_geographic Geographic]

[heading Example]
[densify_strategy]
[densify_strategy_output]

[heading See also]
\* [link geometry.reference.algorithms.line_interpolate line_interpolate]
}
*/
template <typename Geometry, typename Distance, typename Strategy>
inline void densify(Geometry const& geometry,
                    Geometry& out,
                    Distance const& max_distance,
                    Strategy const& strategy)
{
    concepts::check<Geometry>();

    if (max_distance <= Distance(0))
    {
        BOOST_THROW_EXCEPTION(geometry::invalid_input_exception());
    }

    geometry::clear(out);

    resolve_variant::densify
        <
            Geometry
        >::apply(geometry, out, max_distance, strategy);
}

/*!
\brief Densify a geometry
\ingroup densify
\tparam Geometry \tparam_geometry
\tparam Distance A numerical distance measure
\param geometry Input geometry, to be densified
\param out Output geometry, densified version of the input geometry
\param max_distance Distance threshold (in units depending on coordinate system)

\qbk{[include reference/algorithms/densify.qbk]}

\qbk{
[heading Example]
[densify]
[densify_output]

[heading See also]
\* [link geometry.reference.algorithms.line_interpolate line_interpolate]
}
*/
template <typename Geometry, typename Distance>
inline void densify(Geometry const& geometry,
                    Geometry& out,
                    Distance const& max_distance)
{
    densify(geometry, out, max_distance, default_strategy());
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DENSIFY_HPP


/**
 * @file preform_hybrid.cpp
 *
 */

#define AND &&
#define OR ||
#define MISS kFloatMissing

#include "preform_hybrid.h"
#include "forecast_time.h"
#include "level.h"
#include "logger.h"
#include "plugin_factory.h"
#include "util.h"
#include <boost/thread.hpp>
#include <iostream>

#include "hitool.h"

using namespace std;
using namespace himan;
using namespace himan::plugin;

// 0. Mallissa sadetta (RR>0; RR = rainfall + snowfall, [RR]=mm/h)
//
// 1. Jäätävää tihkua, jos
//     RR <= 0.2
//     -10 < T2m < 0
//     sade ei konvektiivista (ConvPre mm/h = 0)
//     stratus (base<300m ja määrä vähintään 5/8)
//     stratuksessa (heikkoa) nousuliikettä (0<wAvg<50mm/s)
//     stratus riittävän paksu (dz>800m)
//     stratus Ttop > -12C
//     stratus avgT > -12C
//     -10C < T2m < 0C
//     kuiva kerros (paksuus>1.5km, jossa N<30%) stratuksen yläpuolella
//
// 2. Jäätävää vesisadetta, jos
//     T2m <= 0
//     riittävän paksu/lämmin (pinta-ala>100mC) sulamiskerros pinnan yläpuolella
//     riittävän paksu/kylmä (pinta-ala<-100mC) pakkaskerros pinnassa sulamiskerroksen alapuolella
//     jos on stratus, sulamiskerros sen yllä ei saa olla kuiva
//
// 3. Tihkua tai vettä, jos
//     riittävän paksu ja lämmin plussakerros pinnan yläpuolella
//
//   3.1 Tihkua, jos
//        RR <= 0.3
//        stratus (base<300m ja määrä vähintään 5/8)
//        stratus riittävän paksu (dz>500m)
//        kuiva kerros (dz>1.5km, jossa N<30%) stratuksen yläpuolella
//
//   3.2 Muuten vettä
//
//   3.3 Jos pinnan plussakerroksessa on kuivaa (rhAvg<rhMelt), muutetaan olomuoto vedestä rännäksi
//
// 4. Räntää, jos
//     ei liian paksu/lämmin plussakerros pinnan yläpuolella
//
//   4.1 Jos pinnan plussakerroksessa on kuivaa (rhAvg<rhMelt), muutetaan olomuoto rännästä lumeksi
//
// 5. Muuten lunta
//     korkeintaan ohut plussakerros pinnan yläpuolella

// Korkein sallittu pilven alarajan korkeus, jotta kysessä stratus [m]
const double baseLimit = 300.;

// Vaadittu min. stratuksen paksuus tihkussa ja jäätävässä tihkussa [m]
const double stLimit = 500.;
const double fzStLimit = 800.;

// Kylmin sallittu stratuksen topin T ja kylmin sallittu st:n keskim. T [C] jäätävässä tihkussa
const double stTlimit = -12.;

// Kynnysarvo "riittävän yhtenäisen/ei kerroksittaisen" stratuksen keskim. N-arvolle [%]
const double Nlimit = 70.;

// Vaadittu 2m lämpötilaväli (oltava näiden välissä) [C] jäätävässä tihkussa
const double sfcMax = 0.;
const double sfcMin = -10.;

// Max. sallittu pilven (keskimääräinen) määrä (N) stratuksen yläpuolisessa kerroksessa [%] (jäätävässä) tihkussa
// (käytetään myös jäätävässä sateessa vaadittuna pilven minimimääränä)
const double dryNlim = 0.3;

// Kynnysarvo vaaditulle stratuksen yläpuolisen kuivan kerroksen paksuudelle [m] jää]
// Raja-arvot tihkun ja jäätävän tihkun max intensiteetille [mm/h]
// (pienemmällä jäätävän tihkun raja-arvolla voi hieman rajoittaa sen esiintymistä)
const double dzLim = 0.3;
const double fzdzLim = 0.2;

// Raja-arvot pinnan pakkaskerroksen (MA) ja sen yläpuolisen sulamiskerroksen (PA) pinta-alalle jäätävässä sateessa [mC]
const double fzraMA = -100.;
const double fzraPA = 100.;

// Pinnan yläpuolisen plussakerroksen pinta-alan raja-arvot [mC, "metriastetta"]:
const double waterArea = 200.;  // alkup. PK:n arvo oli 300
const double snowArea = 50.;    // alkup. PK:n arvo oli 50

// Max sallittu nousuliike st:ssa [mm/s]
const double wMax = 50.;

const param stratusBaseParam("STRATUS-BASE-M");
const param stratusTopParam("STRATUS-TOP-M");
const param stratusTopTempParam("STRATUS-TOP-T-K");
const param stratusMeanTempParam("STRATUS-MEAN-T-K");
const param stratusMeanCloudinessParam("STRATUS-MEAN-N-PRCNT");
const param stratusUpperLayerNParam("STRATUS-UPPER-LAYER-N-PRCNT");
const param stratusVerticalVelocityParam("STRATUS-VERTICAL-VELOCITY-MMS");

const param minusAreaParam("MINUS-AREA-MC");        // metriastetta, mC
const param plusAreaParam("PLUS-AREA-MC");          // metriastetta, mC
const param plusAreaSfcParam("PLUS-AREA-SFC-MC");   // metriastetta, mC
const param numZeroLevelsParam("NUMZEROLEVELS-N");  // nollakohtien lkm
const param rhAvgParam("RHAVG-PRCNT");
const param rhAvgUpperParam("RHAVG-UPPER-PRCNT");
const param rhMeltParam("RHMELT-PRCNT");
const param rhMeltUpperParam("RHMELT-UPPER-PRCNT");

preform_hybrid::preform_hybrid()
{
	itsLogger = logger("preform_hybrid");
}

void preform_hybrid::Process(std::shared_ptr<const plugin_configuration> conf)
{
	// Initialize plugin

	Init(conf);

	vector<param> params({param("PRECFORM2-N", 1206, 0, 1, 19)});

	if (itsConfiguration->Exists("potential_precipitation_form") &&
	    itsConfiguration->GetValue("potential_precipitation_form") == "true")
	{
		params.push_back(param("POTPRECF-N", 1226, 0, 1, 254));
	}

	SetParams(params);

	Start();
}

/*
 * Calculate()
 *
 * This function does the actual calculation.
 */

void preform_hybrid::Calculate(shared_ptr<info> myTargetInfo, unsigned short threadIndex)
{
	assert(fzStLimit >= stLimit);

	// Required source parameters

	params RRParam({param("RR-1-MM"), param("RRR-KGM2")});  // one hour prec OR precipitation rate (HHsade)
	const param TParam("T-K");
	const param RHParam("RH-PRCNT");

	level surface0mLevel(kHeight, 0);
	level surface2mLevel(kHeight, 2);

	auto myThreadedLogger = logger("preformHybridThread #" + to_string(threadIndex));

	forecast_time forecastTime = myTargetInfo->Time();
	level forecastLevel = myTargetInfo->Level();
	forecast_type forecastType = myTargetInfo->ForecastType();

	myThreadedLogger.Info("Calculating time " + static_cast<string>(forecastTime.ValidDateTime()) + " level " +
	                      static_cast<string>(forecastLevel));

	// Source infos

	info_t RRInfo = Fetch(forecastTime, surface0mLevel, RRParam, forecastType, false);
	info_t TInfo = Fetch(forecastTime, surface2mLevel, TParam, forecastType, false);

	if (!RRInfo || !TInfo)
	{
		myThreadedLogger.Warning("Skipping step " + to_string(forecastTime.Step()) + ", level " +
		                         static_cast<string>(forecastLevel));
		return;
	}

	info_t stratus;
	info_t freezingArea;

	/*
	 * Spinoff thread will calculate freezing area while main thread calculates
	 * stratus.
	 *
	 * The constructor of std::thread (and boost::thread) deduces argument types
	 * and stores them *by value*.
	 */

	boost::thread t(&preform_hybrid::FreezingArea, this, itsConfiguration, forecastTime, forecastType,
	                boost::ref(freezingArea), myTargetInfo->Grid());

	Stratus(itsConfiguration, forecastTime, forecastType, stratus, myTargetInfo->Grid());

	t.join();

	if (!stratus)
	{
		myThreadedLogger.Error("stratus calculation failed, unable to proceed");
		return;
	}

	if (!freezingArea)
	{
		myThreadedLogger.Error("freezingArea calculation failed, unable to proceed");
		return;
	}

	freezingArea->First();
	stratus->First();

	myThreadedLogger.Info("Stratus and freezing area calculated");

	string deviceType = "CPU";

	assert(myTargetInfo->SizeLocations() == stratus->SizeLocations());
	assert(myTargetInfo->SizeLocations() == freezingArea->SizeLocations());
	assert(myTargetInfo->SizeLocations() == TInfo->SizeLocations());
	assert(myTargetInfo->SizeLocations() == RRInfo->SizeLocations());

	myTargetInfo->FirstParam();

	bool noPotentialPrecipitationForm = (myTargetInfo->SizeParams() == 1);

	int DRIZZLE = 0;
	int RAIN = 1;
	int SLEET = 2;
	int SNOW = 3;
	int FREEZING_DRIZZLE = 4;
	int FREEZING_RAIN = 5;

	LOCKSTEP(myTargetInfo, stratus, freezingArea, TInfo, RRInfo)
	{
		stratus->Param(stratusBaseParam);
		double base = stratus->Value();

		stratus->Param(stratusTopParam);
		double top = stratus->Value();

		stratus->Param(stratusUpperLayerNParam);
		double upperLayerN = stratus->Value();

		stratus->Param(stratusVerticalVelocityParam);
		double wAvg = stratus->Value();

		stratus->Param(stratusMeanCloudinessParam);
		double Navg = stratus->Value();

		stratus->Param(stratusMeanTempParam);
		double stTavg = stratus->Value();

		stratus->Param(stratusTopTempParam);
		double Ttop = stratus->Value();

		freezingArea->Param(plusAreaParam);
		double plusArea = freezingArea->Value();

		freezingArea->Param(plusAreaSfcParam);
		double plusAreaSfc = freezingArea->Value();

		freezingArea->Param(minusAreaParam);
		double minusArea = freezingArea->Value();

		freezingArea->Param(numZeroLevelsParam);
		double nZeroLevel = freezingArea->Value();

		freezingArea->Param(rhAvgParam);
		double rhAvg = freezingArea->Value();

		freezingArea->Param(rhAvgUpperParam);
		double rhAvgUpper = freezingArea->Value();

		freezingArea->Param(rhMeltParam);
		double rhMelt = freezingArea->Value();

		freezingArea->Param(rhMeltUpperParam);
		double rhMeltUpper = freezingArea->Value();

		double RR = RRInfo->Value();
		double T = TInfo->Value();

		if (RR == MISS || T == MISS)
		{
			continue;
		}

		if (RR == 0 && noPotentialPrecipitationForm)
		{
			continue;
		}

		double PreForm = MISS;

		// Unit conversions

		T -= himan::constants::kKelvin;  // K --> C

		if (Ttop != MISS)
		{
			Ttop -= himan::constants::kKelvin;
		}

		if (stTavg != MISS)
		{
			stTavg -= himan::constants::kKelvin;
		}

		if (Navg != MISS)
		{
			Navg *= 100;  // --> %
		}

		assert(T >= -80 && T < 80);
		assert(!noPotentialPrecipitationForm || RR > 0);
		assert(Navg == MISS || (Navg >= 0 && Navg <= 100));

		// Start algorithm
		// Possible values for preform: 0 = tihku, 1 = vesi, 2 = räntä, 3 = lumi, 4 = jäätävä tihku, 5 = jäätävä sade

		// 1. jäätävää tihkua? (tai lumijyväsiä)

		if (base != MISS AND top != MISS AND upperLayerN != MISS AND wAvg != MISS AND Navg != MISS AND stTavg !=
		    MISS AND Ttop !=
		    MISS AND RR <=
		        fzdzLim AND
		            base<baseLimit AND(top - base) >= fzStLimit AND wAvg<wMax AND wAvg >= 0 AND Navg> Nlimit AND Ttop>
		                stTlimit AND stTavg > stTlimit AND T > sfcMin AND T <= sfcMax AND upperLayerN < dryNlim)
		{
			PreForm = FREEZING_DRIZZLE;
		}

		// 2. jäätävää vesisadetta? (tai jääjyväsiä (ice pellets), jos pakkaskerros hyvin paksu, ja/tai sulamiskerros
		// ohut)
		// Löytyykö riittävän paksut: pakkaskerros pinnasta ja sen yläpuolelta plussakerros, jossa pilveä/ei liian
		// kuivaa?
		// (Huom. hyvin paksu pakkaskerros (tai ohut sulamiskerros) -> oikeasti jääjyväsiä/ice pellets fzra sijaan)

		if (PreForm == MISS AND plusArea != MISS AND minusArea != MISS AND rhAvgUpper != MISS AND rhMeltUpper !=
		    MISS AND plusArea >
		        fzraPA AND minusArea<fzraMA AND T <= 0 AND(upperLayerN == MISS OR upperLayerN > dryNlim) AND rhAvgUpper>
		            rhMeltUpper)
		{
			PreForm = FREEZING_RAIN;
		}

		// 3. Lunta, räntää, tihkua vai vettä? PK:n koodia mukaillen

		if (PreForm == MISS)
		{
			// Tihkua tai vettä jos "riitävän paksu lämmin kerros pinnan yläpuolella"

			if (plusArea != MISS AND plusArea > waterArea)
			{
				// Tihkua jos riittävän paksu stratus heikolla sateen intensiteetillä ja yläpuolella kuiva kerros
				// AND (ConvPre=0) poistettu alla olevasta (ConvPre mm/h puuttuu EC:stä; Hirlam-versiossa pidetään
				// mukana)
				if (base != MISS && top != MISS && Navg != MISS && upperLayerN != MISS && RR <= dzLim &&
				    base < baseLimit && (top - base) > stLimit && Navg > Nlimit && upperLayerN < dryNlim)
				{
					PreForm = DRIZZLE;
				}
				else
				{
					PreForm = RAIN;
				}

				// Jos pinnan plussakerroksessa on kuivaa, korjataan olomuodoksi räntä veden sijaan

				if (nZeroLevel != MISS AND rhAvg != MISS AND rhMelt != MISS AND nZeroLevel ==
				    1 AND rhAvg < rhMelt AND plusArea < 4000)
				{
					PreForm = SLEET;
				}

				// Lisäys, jolla korjataan vesi/tihku lumeksi, jos pintakerros pakkasella (mutta jäätävän sateen/tihkun
				// kriteerit eivät toteudu,
				// esim. paksu plussakerros pakkas-st/sc:n yllä)
				if (minusArea != MISS OR(plusAreaSfc != MISS AND plusAreaSfc < snowArea))
				{
					PreForm = SNOW;
				}
			}

			// Räntää jos "ei liian paksu lämmin kerros pinnan yläpuolella"

			if (plusArea != MISS AND plusArea >= snowArea AND plusArea <= waterArea)
			{
				PreForm = SLEET;

				// Jos pinnan plussakerroksessa on kuivaa, korjataan olomuodoksi lumi rännän sijaan

				if (nZeroLevel != MISS AND rhAvg != MISS AND rhMelt != MISS AND nZeroLevel == 1 AND rhAvg < rhMelt)
				{
					PreForm = SNOW;
				}

				// lisäys, jolla korjataan räntä lumeksi, kun pintakerros pakkasella tai vain ohuelti plussalla

				if (minusArea != MISS OR(plusAreaSfc != MISS AND plusAreaSfc < snowArea))
				{
					PreForm = SNOW;
				}
			}

			// Muuten lunta (PlusArea<50: "korkeintaan ohut lämmin kerros pinnan yläpuolella")

			if (plusArea == MISS OR plusArea < snowArea)
			{
				PreForm = SNOW;
			}
		}

		// FINISHED

		if (RR == 0)
		{
			// If RR is zero, we can only have potential prec form
			myTargetInfo->ParamIndex(1);
			myTargetInfo->Value(PreForm);
		}
		else
		{
			// If there is precipitation, we have at least regular prec form
			myTargetInfo->ParamIndex(0);
			myTargetInfo->Value(PreForm);

			if (!noPotentialPrecipitationForm)
			{
				// Also potential prec form
				myTargetInfo->ParamIndex(1);
				myTargetInfo->Value(PreForm);
			}
		}
	}

	myThreadedLogger.Info("[" + deviceType + "] Missing values: " + to_string(myTargetInfo->Data().MissingCount()) +
	                      "/" + to_string(myTargetInfo->Data().Size()));
}

void preform_hybrid::FreezingArea(shared_ptr<const plugin_configuration> conf, const forecast_time& ftime,
                                  const forecast_type& ftype, shared_ptr<info>& result, const grid* baseGrid)
{
	auto h = GET_PLUGIN(hitool);

	h->Configuration(conf);
	h->Time(ftime);
	h->ForecastType(ftype);

	vector<param> params = {minusAreaParam, plusAreaParam,   plusAreaSfcParam, numZeroLevelsParam,
	                        rhAvgParam,     rhAvgUpperParam, rhMeltParam,      rhMeltUpperParam};
	vector<forecast_time> times = {ftime};
	vector<level> levels = {level(kHeight, 0, "HEIGHT")};

	auto ret = make_shared<info>(*conf->Info());
	ret->Params(params);
	ret->Levels(levels);
	ret->Times(times);
	ret->Create(baseGrid);

	vector<double> constData1(ret->Data().Size(), 0);

	auto constData2 = constData1;
	fill(constData2.begin(), constData2.end(), 5000);

	auto constData3 = constData1;
	fill(constData3.begin(), constData3.end(), himan::constants::kKelvin);  // 0C

	vector<double> numZeroLevels, zeroLevel1, zeroLevel2, zeroLevel3, zeroLevel4;
	vector<double> Tavg01, Tavg12, Tavg23, Tavg34;
	vector<double> plusArea, minusArea, plusAreaSfc;
	vector<double> rhAvg01, rhAvgUpper12, rhAvgUpper23;

	auto log = logger("preform_hybrid-freezing_area");

	try
	{
		// 0-kohtien lkm pinnasta (yläraja 5km, jotta ylinkin nollakohta varmasti löytyy)

		param wantedParam("T-K");

		log.Trace("Counting number of zero levels");

		numZeroLevels = h->VerticalCount(wantedParam, constData1, constData2, constData3);

		ret->Param(numZeroLevelsParam);
		ret->Data().Set(numZeroLevels);

#ifdef DEBUG
		for (size_t i = 0; i < numZeroLevels.size(); i++)
		{
			assert(numZeroLevels[i] != MISS);
		}

		util::DumpVector(numZeroLevels, "num zero levels");
#endif

		zeroLevel1.resize(numZeroLevels.size(), MISS);
		zeroLevel2.resize(numZeroLevels.size(), MISS);
		zeroLevel3.resize(numZeroLevels.size(), MISS);
		zeroLevel4.resize(numZeroLevels.size(), MISS);

		rhAvgUpper12.resize(numZeroLevels.size(), MISS);
		rhAvgUpper23.resize(numZeroLevels.size(), MISS);

		// Keskim. lämpötila 1. nollarajan alapuolella, 1/2. ja 2/3. nollarajojen välisissä kerroksissa [C]
		Tavg01.resize(numZeroLevels.size(), MISS);
		Tavg12.resize(numZeroLevels.size(), MISS);
		Tavg23.resize(numZeroLevels.size(), MISS);
		Tavg34.resize(numZeroLevels.size(), MISS);

		// 1. nollarajan alapuolisen, 2/3. nollarajojen välisen, ja koko T>0 alueen koko [mC, "metriastetta"]
		plusArea = zeroLevel1;
		plusAreaSfc = zeroLevel1;

		// Mahdollisen pinta- tai 1/2. nollarajojen välisen pakkaskerroksen koko [mC, "metriastetta"]
		minusArea = zeroLevel1;

		log.Trace("Searching for first zero level height");
		zeroLevel1 = h->VerticalHeight(wantedParam, constData1, constData2, constData3, 1);

#ifdef DEBUG
		util::DumpVector(zeroLevel1, "zero level 1");
#endif

		log.Trace("Searching for average temperature between ground level and first zero level");
		Tavg01 = h->VerticalAverage(wantedParam, constData1, zeroLevel1);

#ifdef DEBUG
		util::DumpVector(Tavg01, "tavg 01");
#endif

		wantedParam = param("RH-PRCNT");

		log.Trace("Searching for average humidity between ground and first zero level");
		// Keskimääräinen RH nollarajan alapuolisessa plussakerroksessa
		rhAvg01 = h->VerticalAverage(wantedParam, constData1, zeroLevel1);

#ifdef DEBUG
		util::DumpVector(rhAvg01, "rh avg 01");
#endif

		// Only the first zero layer with at least one non-missing element is required
		// to be present. All other zero layers (2,3,4) are optional.

		try
		{
			// Values between zero levels 1 <--> 2
			wantedParam = param("T-K");

			log.Trace("Searching for second zero level height");
			zeroLevel2 = h->VerticalHeight(wantedParam, constData1, constData2, constData3, 2);

#ifdef DEBUG
			util::DumpVector(zeroLevel2, "zero level 2");
#endif

			log.Trace("Searching for average temperature between first and second zero level");
			Tavg12 = h->VerticalAverage(wantedParam, zeroLevel1, zeroLevel2);

#ifdef DEBUG
			util::DumpVector(Tavg12, "tavg 12");
#endif

			log.Trace("Searching for average humidity between first and second zero level");

			// Keskimääräinen RH pakkaskerroksen yläpuolisessa plussakerroksessa
			wantedParam = param("RH-PRCNT");

			rhAvgUpper12 = h->VerticalAverage(wantedParam, zeroLevel1, zeroLevel2);

#ifdef DEBUG
			util::DumpVector(rhAvgUpper12, "rh avg upper 12");
#endif

			// 2 <--> 3
			wantedParam = param("T-K");

			log.Trace("Searching for third zero level height");
			zeroLevel3 = h->VerticalHeight(wantedParam, constData1, constData2, constData3, 3);

#ifdef DEBUG
			util::DumpVector(zeroLevel3, "zero level 3");
#endif

			log.Trace("Searching for average temperature between second and third zero level");
			Tavg23 = h->VerticalAverage(wantedParam, zeroLevel2, zeroLevel3);

#ifdef DEBUG
			util::DumpVector(Tavg23, "tavg 23");
#endif

			wantedParam = param("RH-PRCNT");

			log.Trace("Searching for average humidity between second and third zero level");

			// Keskimääräinen RH ylemmässä plussakerroksessa
			rhAvgUpper23 = h->VerticalAverage(wantedParam, zeroLevel2, zeroLevel3);

#ifdef DEBUG
			util::DumpVector(rhAvgUpper23, "rh avg upper 23");
#endif

			// 3 <--> 4
			wantedParam = param("T-K");

			log.Trace("Searching for fourth zero level height");
			zeroLevel4 = h->VerticalHeight(wantedParam, constData1, constData2, constData3, 4);

#ifdef DEBUG
			util::DumpVector(zeroLevel4, "zero level 4");
#endif

			log.Trace("Searching for average temperature between third and fourth zero level");
			Tavg34 = h->VerticalAverage(wantedParam, zeroLevel3, zeroLevel4);

#ifdef DEBUG
			util::DumpVector(Tavg34, "tavg 34");
#endif
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				log.Debug("Some zero level not found from entire data");
			}
		}
	}
	catch (const HPExceptionType& e)
	{
		if (e != kFileDataNotFound)
		{
			throw runtime_error("FreezingArea() caught exception " + to_string(e));
		}
		else
		{
			return;
		}
	}

	// Keskimääräinen rhMelt nollarajan alapuolisessa plussakerroksessa, ja pakkaskerroksen yläpuolisessa
	// plussakerroksessa
	vector<double> rhMeltUpper(rhAvg01.size(), MISS);
	vector<double> rhMelt(rhAvg01.size(), MISS);

	// Keskimääräinen RH nollarajan alapuolisessa plussakerroksessa, ja pakkaskerroksen yläpuolisessa plussakerroksessa
	vector<double> rhAvgUpper(rhAvg01.size(), MISS);
	vector<double> rhAvg(rhAvg01.size(), MISS);

	for (size_t i = 0; i < numZeroLevels.size(); i++)
	{
		short numZeroLevel = static_cast<short>(numZeroLevels[i]);

		double pa = MISS, ma = MISS, pasfc = MISS;

		// Kommentteja Simolta nollakohtien lukumäärään:
		// * Nollakohtien löytymättömyys ei ole ongelma, sillä tällöin olomuoto on aina lumi tai jäätävä tihku
		// * Parittomilla nollakohdilla ei voi tulla koskaan jäätävää sadetta koska pintakerros on plussalla
		//   (jolloin ainakin yleensä pätee oletus ettei jäätävää sadetta voi esiintyä)

		// nollarajoja parillinen määrä (pintakerros pakkasella)

		if (numZeroLevel % 2 == 0)
		{
			double zl1 = zeroLevel1[i], zl2 = zeroLevel2[i];
			double ta1 = Tavg01[i], ta2 = Tavg12[i];

			double paloft = MISS;

			if (zl1 != MISS && zl2 != MISS && ta1 != MISS && ta2 != MISS)
			{
				ta1 -= constants::kKelvin;
				ta2 -= constants::kKelvin;
				ma = zl1 * ta1;
				paloft = (zl2 - zl1) * ta2;

				// Keskimääräinen rhMelt pakkaskerroksen yläpuolisessa plussakerroksessa
				rhMeltUpper[i] = 9.5 * exp((-17.27 * ta2) / (ta2 + 238.3)) * (10.5 - ta2);
			}

			rhAvgUpper[i] = rhAvgUpper12[i];

			// Jos ylempänä toinen T>0 kerros, lasketaan myös sen koko (vähintään 4 nollarajaa) ja lisätään se alemman
			// kerroksen kokoon
			// (mahdollisista vielä ylempänä olevista plussakerroksista ei välitetä)
			// (tässäkin pitäisi tarkkaan ottaen tutkia rhAvgUpper, eli sulaako kerroksen läpi putoava lumi)

			if (numZeroLevel >= 4)
			{
				double zl3 = zeroLevel3[i], zl4 = zeroLevel4[i];
				ta2 = Tavg34[i];

				if (zl3 != MISS && zl4 != MISS && ta2 != MISS && paloft != MISS)
				{
					paloft = paloft + (zl4 - zl3) * (ta2 - constants::kKelvin);
				}
			}

			pa = paloft;
		}

		// nollarajoja pariton määrä (pintakerros plussalla)

		else if (numZeroLevel % 2 == 1)
		{
			double zl1 = zeroLevel1[i], ta1 = Tavg01[i];
			double paloft = MISS;

			if (zl1 != MISS && ta1 != MISS)
			{
				ta1 -= constants::kKelvin;
				pasfc = zl1 * ta1;
				pa = pasfc;

				// Lisäys 3.2.2015: Suhteellisen kosteuden (raja-) arvot pinnan plussakerroksessa:
				// Jos nollarajan alapuolisessa plussakerroksessa on kuivaa, asetetaan olomuodoksi lumi
				// (Shaviv, 2006: http://www.sciencebits.com/SnowAboveFreezing)

				// rhMelt = suht. kosteuden raja-arvo, jota pienemmillä kosteuksilla lumihiutaleet eivät sula

				// Keskimääräinen rhMelt nollarajan alapuolisessa plussakerroksessa

				rhMelt[i] = 9.5 * exp((-17.27 * ta1) / (ta1 + 238.3)) * (10.5 - ta1);
			}

			// Keskimääräinen RH nollarajan alapuolisessa plussakerroksessa
			rhAvg[i] = rhAvg01[i];

			// Jos ylempänä toinen T>0 kerros, lasketaan myös sen koko (vähintään 3 nollarajaa)
			// (mahdollisista vielä ylempänä olevista plussakerroksista ei välitetä)

			if (numZeroLevel >= 3)
			{
				double zl2 = zeroLevel2[i], zl3 = zeroLevel3[i];
				double ta2 = Tavg23[i];

				if (zl2 != MISS && zl3 != MISS && ta2 != MISS)
				{
					ta2 -= constants::kKelvin;

					paloft = (zl3 - zl2) * ta2;

					// Keskimääräinen rhMelt ylemmässä plussakerroksessa
					rhMeltUpper[i] = 9.5 * exp((-17.27 * ta2) / (ta2 + 238.3)) * (10.5 - ta2);

					// Keskimääräinen RH ylemmässä plussakerroksessa
					rhAvgUpper[i] = rhAvgUpper23[i];

					if (rhAvgUpper[i] != MISS AND rhMeltUpper[i] != MISS AND rhAvgUpper[i] > rhMeltUpper[i] &&
					    pasfc != MISS)
					{
						pa = pasfc + paloft;
					}
				}
			}
		}

		plusArea[i] = pa;
		plusAreaSfc[i] = pasfc;
		minusArea[i] = ma;
	}

#ifdef DEBUG
	util::DumpVector(minusArea);
	util::DumpVector(plusArea);
	util::DumpVector(plusAreaSfc);
	util::DumpVector(rhAvg);
	util::DumpVector(rhAvgUpper);
	util::DumpVector(rhMelt);
	util::DumpVector(rhMeltUpper);
#endif

	ret->Param(minusAreaParam);
	ret->Data().Set(minusArea);

	ret->Param(plusAreaParam);
	ret->Data().Set(plusArea);

	ret->Param(plusAreaSfcParam);
	ret->Data().Set(plusAreaSfc);

	ret->Param(rhAvgParam);
	ret->Data().Set(rhAvg);

	ret->Param(rhAvgUpperParam);
	ret->Data().Set(rhAvgUpper);

	ret->Param(rhMeltParam);
	ret->Data().Set(rhMelt);

	ret->Param(rhMeltUpperParam);
	ret->Data().Set(rhMeltUpper);

	result = ret;
}

void preform_hybrid::Stratus(shared_ptr<const plugin_configuration> conf, const forecast_time& ftime,
                             const forecast_type& ftype, shared_ptr<info>& result, const grid* baseGrid)
{
	auto h = GET_PLUGIN(hitool);

	h->Configuration(conf);
	h->Time(ftime);
	h->ForecastType(ftype);

	// Kerroksen paksuus pinnasta [m], josta etsitään stratusta (min. BaseLimit+FZstLimit)
	const double layer = 2000.;

	// N-kynnysarvo vaaditulle min. stratuksen määrälle [%] (50=yli puoli taivasta):
	const double stCover = 0.5;

	// Kynnysarvo vaaditulle stratuksen yläpuolisen kuivan kerroksen paksuudelle [m] (jäätävässä) tihkussa:
	const double drydz = 1500.;

	vector<param> params = {
	    stratusBaseParam,           stratusTopParam,         stratusTopTempParam,         stratusMeanTempParam,
	    stratusMeanCloudinessParam, stratusUpperLayerNParam, stratusVerticalVelocityParam};
	vector<forecast_time> times = {ftime};
	vector<level> levels = {level(kHeight, 0, "HEIGHT")};

	auto ret = make_shared<info>(*conf->Info());
	ret->Params(params);
	ret->Levels(levels);
	ret->Times(times);
	ret->Create(baseGrid);

	vector<double> constData1(ret->Data().Size(), 0);

	auto constData2 = constData1;
	auto log = logger("preform_hybrid-stratus");

	try
	{
		// baseThreshold ja topThreshold:
		// (Paikan mukaan vaihtuva) N-kynnysarvo stratuksen ala- ja ylärajalle [%] (tarkkaa stCover arvoa ei aina löydy)

		vector<param> wantedParamList({param("N-0TO1"), param("N-PRCNT")});

		log.Info("Searching for stratus lower limit");

		auto baseThreshold = h->VerticalMinimum(wantedParamList, 0, stLimit);

		for (size_t i = 0; i < baseThreshold.size(); i++)
		{
			assert(baseThreshold[i] != MISS);

			if (baseThreshold[i] < stCover)
			{
				baseThreshold[i] = stCover;
			}
		}

#ifdef DEBUG
		util::DumpVector(baseThreshold);
#endif

		ret->Param(stratusBaseParam);
		ret->Data().Set(baseThreshold);

		log.Info("Searching for stratus upper limit");

		auto topThreshold = h->VerticalMinimum(wantedParamList, stLimit, layer);

		for (size_t i = 0; i < topThreshold.size(); i++)
		{
			assert(topThreshold[i] != MISS);
			if (topThreshold[i] < stCover)
			{
				topThreshold[i] = stCover;
			}
		}

#ifdef DEBUG
		util::DumpVector(topThreshold);
#endif

		// Stratus Base/top [m]
		// _findh: 0 = viimeinen löytyvä arvo pinnasta ylöspäin, 1 = ensimmäinen löytyvä arvo

		log.Info("Searching for stratus base accurate value");

		auto stratusBase = h->VerticalHeight(wantedParamList, 0, stLimit, baseThreshold);

		ret->Param(stratusBaseParam);
		ret->Data().Set(stratusBase);

#ifdef DEBUG
		util::DumpVector(stratusBase);
#endif

		log.Info("Searching for stratus top accurate value");
		auto stratusTop = h->VerticalHeight(wantedParamList, stLimit, layer, topThreshold, 0);

		ret->Param(stratusTopParam);
		ret->Data().Set(stratusTop);

#ifdef DEBUG
		util::DumpVector(stratusTop);
#endif

		try
		{
			log.Info("Searching for cloudiness in layers above stratus top");

			assert(constData1.size() == constData2.size() && constData1.size() == stratusTop.size());

			for (size_t i = 0; i < constData1.size(); i++)
			{
				if (stratusTop[i] == MISS)
				{
					constData1[i] = MISS;
					constData2[i] = MISS;
				}
				else
				{
					constData1[i] = stratusTop[i] + 100;
					constData2[i] = stratusTop[i] + drydz;
				}
			}

			auto upperLayerN = h->VerticalAverage(wantedParamList, constData1, constData2);

			ret->Param(stratusUpperLayerNParam);
			ret->Data().Set(upperLayerN);

#ifdef DEBUG
			util::DumpVector(upperLayerN);
#endif

			log.Info("Searching for stratus mean cloudiness");

			// Stratuksen keskimääräinen N
			auto stratusMeanN = h->VerticalAverage(wantedParamList, stratusBase, stratusTop);

#ifdef DEBUG
			util::DumpVector(stratusMeanN);
#endif

			ret->Param(stratusMeanCloudinessParam);
			ret->Data().Set(stratusMeanN);

			log.Info("Searching for stratus top temperature");

			param wantedParam("T-K");

			// Stratuksen Topin lämpötila (jäätävä tihku)
			auto stratusTopTemp = h->VerticalValue(wantedParam, stratusTop);

#ifdef DEBUG
			util::DumpVector(stratusTopTemp);
#endif

			ret->Param(stratusTopTempParam);
			ret->Data().Set(stratusTopTemp);

			log.Info("Searching for stratus mean temperature");

			for (size_t i = 0; i < constData1.size(); i++)
			{
				double lower = stratusBase[i];
				double upper = stratusTop[i];

				if (lower == MISS || upper == MISS)
				{
					constData1[i] = MISS;
					constData2[i] = MISS;
				}
				else if (fabs(lower - upper) < 100)
				{
					constData1[i] = lower;
					constData2[i] = upper;
				}
				else
				{
					constData1[i] = lower + 50;
					constData2[i] = upper - 50;
				}
			}

			// St:n keskimääräinen lämpötila (poissulkemaan kylmät <-10C stratukset, joiden toppi >-10C) (jäätävä tihku)
			auto stratusMeanTemp = h->VerticalAverage(wantedParam, constData1, constData2);

#ifdef DEBUG
			util::DumpVector(stratusMeanTemp);
#endif

			ret->Param(stratusMeanTempParam);
			ret->Data().Set(stratusMeanTemp);

			log.Info("Searching for mean vertical velocity in stratus");

			wantedParam = param("VV-MMS");

			vector<double> stratusVerticalVelocity;

			try
			{
				// Keskimääräinen vertikaalinopeus st:ssa [mm/s]
				stratusVerticalVelocity = h->VerticalAverage(wantedParam, stratusBase, stratusTop);
			}
			catch (const HPExceptionType& e)
			{
				if (e == kFileDataNotFound)
				{
					log.Debug("Trying for param VV-MS");
					wantedParam = param("VV-MS");

					stratusVerticalVelocity = h->VerticalAverage(wantedParam, stratusBase, stratusTop);

					for (double& d : stratusVerticalVelocity)
					{
						if (d != kFloatMissing) d *= 1000;
					}
				}
			}

#ifdef DEBUG
			util::DumpVector(stratusVerticalVelocity, "stratus vertical velocity");
#endif

			ret->Param(stratusVerticalVelocityParam);
			ret->Data().Set(stratusVerticalVelocity);
		}
		catch (const HPExceptionType& e)
		{
			if (e != kFileDataNotFound)
			{
				throw runtime_error("Stratus() caught exception " + to_string(e));
			}
		}
	}
	catch (const HPExceptionType& e)
	{
		if (e != kFileDataNotFound)
		{
			throw runtime_error("Stratus() caught exception " + to_string(e));
		}
		else
		{
			return;
		}
	}

	result = ret;
}