/** @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 "DocNode.h"

class EsiParser
{
public:
  EsiParser(unsigned max_doc_size);

  /** clears state */
  void clear();

  /** parses a chunk of the document; adds complete nodes found;
   * data is assumed to be NULL-terminated is data_len is set to -1.
   *
   * Output nodes contain pointers to internal data; use with care */
  bool parseChunk(const char *data, EsiLib::DocNodeList &node_list, int data_len = -1);

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

  /** completes the parse including data from previous chunk(s);
   * adds newly found nodes to list. optionally a final or the
   * only chunk can be provided.
   *
   * Output nodes contain pointers to internal data; use with care. */
  bool
  completeParse(EsiLib::DocNodeList &node_list, const char *data = nullptr, int data_len = -1)
  {
    return _completeParse(_data, _parse_start_pos, _orig_output_list_size, node_list, data, data_len);
  }

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

  /** stateless method that parses data and returns output nodes that
   * point to addresses in caller space */
  bool parse(EsiLib::DocNodeList &node_list, const char *ext_data_ptr, int data_len = -1) const;

  /** convenient alternative to method above */
  bool
  parse(EsiLib::DocNodeList &node_list, const std::string &ext_data) const
  {
    return parse(node_list, ext_data.data(), ext_data.size());
  }

  virtual ~EsiParser();

private:
  struct EsiNodeInfo {
    EsiLib::DocNode::TYPE type;
    const char           *tag_suffix;
    int                   tag_suffix_len;
    const char           *closing_tag;
    int                   closing_tag_len;
    EsiNodeInfo(EsiLib::DocNode::TYPE t, const char *s, int s_len, const char *ct, int ct_len)
      : type(t), tag_suffix(s), tag_suffix_len(s_len), closing_tag(ct), closing_tag_len(ct_len){};
  };

  const unsigned _max_doc_size;

  std::string _data;
  int         _parse_start_pos;
  size_t      _orig_output_list_size = 0;

  static const EsiNodeInfo ESI_NODES[];
  static const EsiNodeInfo HTML_COMMENT_NODE_INFO;

  static const char *ESI_TAG_PREFIX;
  static const int   ESI_TAG_PREFIX_LEN;

  static const std::string SRC_ATTR_STR;
  static const std::string TEST_ATTR_STR;
  static const std::string HANDLER_ATTR_STR;

  enum MATCH_TYPE {
    NO_MATCH,
    COMPLETE_MATCH,
    PARTIAL_MATCH,
  };

  MATCH_TYPE _searchData(const std::string &data, size_t start_pos, const char *str, int str_len, size_t &pos) const;

  MATCH_TYPE _compareData(const std::string &data, size_t pos, const char *str, int str_len) const;

  MATCH_TYPE _findOpeningTag(const std::string &data, size_t start_pos, size_t &opening_tag_pos, bool &is_html_comment_node) const;

  bool _parse(const std::string &data, int &parse_start_pos, EsiLib::DocNodeList &node_list, bool last_chunk = false) const;

  bool _processIncludeTag(const std::string &data, size_t curr_pos, size_t end_pos, EsiLib::DocNodeList &node_list) const;

  bool _processSpecialIncludeTag(const std::string &data, size_t curr_pos, size_t end_pos, EsiLib::DocNodeList &node_list) const;

  inline bool _isWhitespace(const char *data, int data_len) const;

  bool _processWhenTag(const std::string &data, size_t curr_pos, size_t end_pos, EsiLib::DocNodeList &node_list) const;

  bool _processChooseTag(const std::string &data, size_t curr_pos, size_t end_pos, EsiLib::DocNodeList &node_list) const;

  bool _processTryTag(const std::string &data, size_t curr_pos, size_t end_pos, EsiLib::DocNodeList &node_list) const;

  inline bool _processSimpleContentTag(EsiLib::DocNode::TYPE node_type, const char *data, int data_len,
                                       EsiLib::DocNodeList &node_list) const;

  bool _setup(std::string &data, int &parse_start_pos, size_t &orig_output_list_size, EsiLib::DocNodeList &node_list,
              const char *data_ptr, int &data_len) const;

  bool _completeParse(std::string &data, int &parse_start_pos, size_t &orig_output_list_size, EsiLib::DocNodeList &node_list,
                      const char *data_ptr = nullptr, int data_len = -1) const;

  inline void _adjustPointers(EsiLib::DocNodeList::iterator node_iter, EsiLib::DocNodeList::iterator end, const char *ext_data_ptr,
                              const char *int_data_start) const;
};
