#ifndef ARABICA_XSLT_COMPILATION_CONTEXT
#define ARABICA_XSLT_COMPILATION_CONTEXT

#include <SAX/XMLReader.hpp>
#include <SAX/helpers/DefaultHandler.hpp>
#include <XML/strings.hpp>
#include <XPath/XPath.hpp>
#include <stack>

#include "xslt_stylesheet_parser.hpp"
#include "xslt_functions.hpp"

namespace Arabica
{
namespace XSLT
{
template<class string_type, class string_adaptor> class CompiledStylesheet;
template<class string_type, class string_adaptor> class ItemContainer;

template<class string_type, class string_adaptor = Arabica::default_string_adaptor<string_type> >
class CompilationContext :
    private Arabica::XPath::FunctionResolver<string_type, string_adaptor>,
    private Arabica::XPath::NamespaceContext<string_type, string_adaptor>,
    private Arabica::XPath::DefaultVariableCompileTimeResolver<string_type, string_adaptor>
{
private:
  typedef Arabica::XPath::DefaultVariableCompileTimeResolver<string_type, string_adaptor> CTVariableResolverT;
  typedef StylesheetConstant<string_type, string_adaptor> SC;

public:
  typedef StylesheetParser<string_type, string_adaptor> StylesheetParserT;
  typedef CompiledStylesheet<string_type, string_adaptor> CompiledStylesheetT;
  typedef SAX::DefaultHandler<string_type, string_adaptor> DefaultHandlerT;
  typedef SAX::ContentHandler<string_type, string_adaptor> ContentHandlerT;
  typedef SAX::Attributes<string_type, string_adaptor> AttributesT;
  typedef Arabica::XPath::XPathExpressionPtr<string_type, string_adaptor> XPathExpressionPtrT;
  typedef Arabica::XPath::MatchExpr<string_type, string_adaptor> MatchExprT;
  typedef XML::QualifiedName<string_type, string_adaptor> QualifiedNameT;


  CompilationContext(StylesheetParserT& parser,
                     CompiledStylesheetT& stylesheet) :
    parser_(parser),
    stylesheet_(stylesheet),
    autoNs_(1),
    current_allowed_(false),
    variables_allowed_(true),    
    precedence_(Precedence::InitialPrecedence())
  {
    xpath_.setNamespaceContext(*this);
    xpath_.setFunctionResolver(*this);
    xpath_.setVariableCompileTimeResolver(*this);
  } // CompilationContext

  ~CompilationContext()
  {
    // delete any left over - will only be the case if unwinding
    while(handlerStack_.size() > 1)
    {
      delete handlerStack_.top();
      handlerStack_.pop();
    } // while ...
  } // ~CompilationContext

  void root(DefaultHandlerT& root)
  {
    handlerStack_.push(&root);
  } // root

  StylesheetParserT& parser() const { return parser_; }
  XPathExpressionPtrT xpath_expression(const string_type& expr) const { return xpath_.compile_expr(expr); } 
  XPathExpressionPtrT xpath_expression_no_variables(const string_type& expr) const
  {
    Disallow variables(variables_allowed_);
    return xpath_expression(expr);
  } // xpath_expression_no_variables
  std::vector<MatchExprT> xpath_match(const string_type& match) const 
  {
    Disallow current(current_allowed_);
    return xpath_.compile_match(match); 
  } // xpath_match
  std::vector<Arabica::XPath::MatchExpr<string_type> > xpath_match_no_variables(const string_type& match) const
  {
    Disallow variables(variables_allowed_);
    return xpath_match(match);
  } // xpath_match_no_variables

  XPathExpressionPtrT xpath_attribute_value_template(const string_type& expr) const { return xpath_.compile_attribute_value_template(expr); } 
  CompiledStylesheetT& stylesheet() const { return stylesheet_; }

  QualifiedNameT processElementQName(const string_type& qName) const
  {
    return parser_.processElementQName(qName);
  } // processElementQName

  QualifiedNameT processInternalQName(const string_type& qName) const
  {
    return parser_.processInternalQName(qName);
  } // processInternalQName

  string_type makeAbsolute(const string_type& href) const
  {
    return parser_.makeAbsolute(href);
  } // makeAbsolute

  string_type setBase(const string_type& href) const
  {
    return parser_.setBase(href);
  } // setBase

  string_type currentBase() const
  {
    return parser_.currentBase();
  } // currentBase

  void push(ItemContainer<string_type, string_adaptor>* parent,
            DefaultHandlerT* newHandler,
            const string_type& namespaceURI,
            const string_type& localName,
            const string_type& qName,
            const AttributesT& atts)
  {
    parentStack_.push(parent);
    handlerStack_.push(newHandler);
    parser_.setContentHandler(*newHandler);
    newHandler->startElement(namespaceURI, localName, qName, atts);
  } // push

  void pop()
  {
    parentStack_.pop();
    delete handlerStack_.top();
    handlerStack_.pop();
    parser_.setContentHandler(*handlerStack_.top());
  } // pop

  ItemContainer<string_type, string_adaptor>& parentContainer() const
  { 
    return *parentStack_.top();
  } // parentContainer

  ContentHandlerT& parentHandler() const
  {
    parser_.setContentHandler(*handlerStack_.top());
    return parser_.contentHandler();
  } // parentHandler

  std::map<string_type, string_type> inScopeNamespaces() const
  {
    return parser_.inScopeNamespaces();
  } // inScopeNamespaces

  void addNamespaceAlias(const string_type& stylesheet_namespace,
			 const string_type& result_prefix,
			 const string_type& result_namespace)
  { 
    namespaceRemap_[stylesheet_namespace] = std::make_pair(result_prefix, result_namespace);
  } // addNamespaceAlias

  bool isRemapped(const string_type& namespaceURI) const
  {
    return namespaceRemap_.find(namespaceURI) != namespaceRemap_.end();
  } // isRemapped

  const std::pair<string_type, string_type>& remappedNamespace(const string_type& namespaceURI) 
  {
    return namespaceRemap_[namespaceURI];
  } // remappedNamespace
  
  string_type autoNamespacePrefix() const
  {
    std::basic_ostringstream<typename string_adaptor::value_type> ss;
    ss << SC::auto_ns << autoNs_++;
    return ss.str();
  } // autoNamespacePrefix

  void set_precedence(const Precedence& prec)
  {
    precedence_ = prec;
  } // set_precedence

  Precedence next_precedence()
  {
    return precedence_.next_generation();
  } // next_precedence

  const Precedence& precedence() const
  {
    return precedence_;
  } // precedence

private:
  virtual Arabica::XPath::XPathExpression_impl<string_type, Arabica::default_string_adaptor<string_type> >* 
    compileVariable(const string_type& namespace_uri, const string_type& name) const 
  {
    if(!variables_allowed_)
      return 0;
    return CTVariableResolverT::compileVariable(namespace_uri, name);
  } // compileVariable

  // FunctionResolver 
  virtual Arabica::XPath::XPathFunction<string_type>* resolveFunction(
                                         const string_type& namespace_uri, 
                                         const string_type& name,
                                         const std::vector<Arabica::XPath::XPathExpression<string_type> >& argExprs) const
  {
    if(!namespace_uri.empty())
      return new UndefinedFunction<string_type, string_adaptor>(namespace_uri, name, argExprs);

    // document
    if(name == DocumentFunction<string_type, string_adaptor>::name())
      return new DocumentFunction<string_type, string_adaptor>(parser_.currentBase(), argExprs);
    // key
    if(name == KeyFunction<string_type, string_adaptor>::name())
      return new KeyFunction<string_type, string_adaptor>(stylesheet_.keys(), parser_.inScopeNamespaces(), argExprs);
    // format-number
    // current
    if((name == CurrentFunction<string_type, string_adaptor>::name()) && (current_allowed_))
      return new CurrentFunction<string_type, string_adaptor>(argExprs);
    // unparsed-entity-uri
    //if(name == UnparsedEntityUriFunction<string_type, string_adaptor>::name())
    //  return new UnparsedEntityUriFunction<string_type, string_adaptor>(argExprs);
    // generate-id
    if(name == GenerateIdFunction<string_type, string_adaptor>::name())
      return new GenerateIdFunction<string_type, string_adaptor>(argExprs);
    if(name == SystemPropertyFunction<string_type, string_adaptor>::name())
      return new SystemPropertyFunction<string_type, string_adaptor>(argExprs);
    // element-available
    if(name == ElementAvailableFunction<string_type, string_adaptor>::name())
    {
      std::vector<std::pair<string_type, string_type> > dummy;
      return new ElementAvailableFunction<string_type, string_adaptor>(dummy, parser_.inScopeNamespaces(), argExprs);
    } 
    // function-available
    if(name == FunctionAvailableFunction<string_type, string_adaptor>::name())
      return new FunctionAvailableFunction<string_type, string_adaptor>(validNames(), parser_.inScopeNamespaces(), argExprs);

    return 0;
  } // resolveFunction

  virtual std::vector<std::pair<string_type, string_type> > validNames() const 
  {
    static string_type functionNames[] = { DocumentFunction<string_type, string_adaptor>::name(), 
                                           KeyFunction<string_type, string_adaptor>::name(), 
                                           /* format-number, */ 
                                           CurrentFunction<string_type, string_adaptor>::name(),
					                                 UnparsedEntityUriFunction<string_type, string_adaptor>::name(), 
                                           GenerateIdFunction<string_type, string_adaptor>::name(), 
                                           SystemPropertyFunction<string_type, string_adaptor>::name(), 
					                                 ElementAvailableFunction<string_type, string_adaptor>::name(),
                                           FunctionAvailableFunction<string_type, string_adaptor>::name(), 
                                           string_adaptor::empty_string() };

    std::vector<std::pair<string_type,string_type> > names;

    for(int i = 0; functionNames[i] != string_adaptor::empty_string(); ++i)
      names.push_back(std::make_pair(string_adaptor::empty_string(), functionNames[i]));

    return names;
  } // validNames

  // NamespaceContext 
  virtual string_type namespaceURI(const string_type& prefix) const
  {
    return parser_.namespaceURI(prefix);
  } // namespaceURI

  typedef std::pair<string_type, string_type> Namespace;

  StylesheetParser<string_type, string_adaptor>& parser_;
  CompiledStylesheetT& stylesheet_;
  mutable int autoNs_;
  mutable bool current_allowed_;
  mutable bool variables_allowed_;
  Precedence precedence_;
  Arabica::XPath::XPath<string_type> xpath_;
  std::stack<SAX::DefaultHandler<string_type>*> handlerStack_;
  std::stack<ItemContainer<string_type, string_adaptor>*> parentStack_;
  std::map<string_type, Namespace> namespaceRemap_;

  CompilationContext(const CompilationContext&);

  class Disallow
  { 
    public:
      Disallow(bool& allow) : allow_(allow) { allow_ = false; }
      ~Disallow() { allow_ = true; }
    private:
      bool& allow_;
  }; // DisallowCurrent 
}; // class CompilationContext

} // namespace XSLT
} // namespace Arabica

#endif // ARABICA_XSLT_COMPILATION_CONTEXT