/** @file

  A brief file description

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 */

#pragma once

#include <string>
#include <map>
#include <pthread.h>
#include "lib/ComponentBase.h"
#include "lib/StringHash.h"
#include "lib/DocNode.h"
#include "EsiParser.h"
#include "HttpDataFetcher.h"
#include "Variables.h"
#include "Expression.h"
#include "SpecialIncludeHandler.h"
#include "HandlerManager.h"

extern pthread_key_t key;
class EsiProcessor : private EsiLib::ComponentBase
{
public:
  enum UsePackedNodeResult {
    PROCESS_IN_PROGRESS,
    UNPACK_FAILURE,
    PROCESS_SUCCESS,
    PROCESS_FAILURE,
  };

  EsiProcessor(const char *debug_tag, const char *parser_debug_tag, const char *expression_debug_tag,
               EsiLib::ComponentBase::Debug debug_func, EsiLib::ComponentBase::Error error_func, HttpDataFetcher &fetcher,
               EsiLib::Variables &variables, const EsiLib::HandlerManager &handler_mgr);

  /** Initializes the processor with the context of the request to be processed */
  bool start();

  /** Adds data to be parsed */
  bool addParseData(const char *data, int data_len = -1);

  /** convenient alternative to method above */
  bool
  addParseData(const std::string &data)
  {
    return addParseData(data.data(), data.size());
  }

  /** Tells processor to wrap-up parsing; a final or the only piece of
   * data can be optionally provided */
  bool completeParse(const char *data = nullptr, int data_len = -1);

  /** convenient alternative to method above */
  bool
  completeParse(const std::string &data)
  {
    return completeParse(data.data(), data.size());
  }

  enum ReturnCode {
    FAILURE,
    SUCCESS,
    NEED_MORE_DATA,
  };

  /** Processes the currently parsed ESI document and returns processed
   * data in supplied out-parameters. Should be called when fetcher has
   * finished pulling in all data.
   *
   * try/attempt/except construct can generate new fetch requests
   * during processing. Only in such cases is NEED_MORE_DATA returned;
   * else FAILURE/SUCCESS is returned. */
  ReturnCode process(const char *&data, int &data_len);

  /** Process the ESI document and flush processed data as much as
   * possible. Can be called when fetcher hasn't finished pulling
   * in all data. */
  ReturnCode flush(std::string &data, int &overall_len);

  /** returns packed version of document currently being processed */
  void
  packNodeList(std::string &buffer, bool retain_buffer_data)
  {
    return _node_list.pack(buffer, retain_buffer_data);
  }

  /** Unpacks previously parsed and packed ESI node list from given
   * buffer and preps for process(); Unpacked document will point to
   * data in argument (i.e., caller space) */
  UsePackedNodeResult usePackedNodeList(const char *data, int data_len);

  /** convenient alternative to method above */
  inline UsePackedNodeResult
  usePackedNodeList(const std::string &data)
  {
    return usePackedNodeList(data.data(), data.size());
  }

  /** Clears state from current request */
  void stop();

  virtual ~EsiProcessor();

private:
  enum EXEC_STATE {
    STOPPED,
    PARSING,
    WAITING_TO_PROCESS,
    PROCESSED,
    ERRORED,
  };
  EXEC_STATE _curr_state;

  std::string _output_data;

  EsiParser _parser;
  EsiLib::DocNodeList _node_list;
  int _n_prescanned_nodes;
  int _n_processed_nodes;
  int _n_processed_try_nodes;
  int _overall_len;

  HttpDataFetcher &_fetcher;
  EsiLib::StringHash _include_urls;

  bool _reqAdded;
  bool _usePackedNodeList;

  bool _processEsiNode(const EsiLib::DocNodeList::iterator &iter);
  bool _handleParseComplete();
  bool _getIncludeData(const EsiLib::DocNode &node, const char **content_ptr = nullptr, int *content_len_ptr = nullptr);
  DataStatus _getIncludeStatus(const EsiLib::DocNode &node);
  bool _handleVars(const char *str, int str_len);
  bool _handleChoose(EsiLib::DocNodeList::iterator &curr_node);
  bool _handleTry(EsiLib::DocNodeList::iterator &curr_node);
  bool _handleHtmlComment(const EsiLib::DocNodeList::iterator &curr_node);
  bool _preprocess(EsiLib::DocNodeList &node_list, int &n_prescanned_nodes);
  inline bool _isWhitespace(const char *data, int data_len);
  void _addFooterData();

  EsiLib::Variables &_esi_vars;
  EsiLib::Expression _expression;

  struct TryBlock {
    EsiLib::DocNodeList &attempt_nodes;
    EsiLib::DocNodeList &except_nodes;
    EsiLib::DocNodeList::iterator pos;
    TryBlock(EsiLib::DocNodeList &att, EsiLib::DocNodeList &exc, EsiLib::DocNodeList::iterator p)
      : attempt_nodes(att), except_nodes(exc), pos(p){};
  };
  typedef std::list<TryBlock> TryBlockList;
  TryBlockList _try_blocks;
  int _n_try_blocks_processed;

  const EsiLib::HandlerManager &_handler_manager;

  static const char *INCLUDE_DATA_ID_ATTR;

  typedef std::map<std::string, EsiLib::SpecialIncludeHandler *> IncludeHandlerMap;
  IncludeHandlerMap _include_handlers;

  void
  error()
  {
    stop();
    _curr_state = ERRORED;
  }
};
