// Copyright 2021 The Chromium Authors
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file or at https://opensource.org/licenses/MIT.

#ifndef THIRD_PARTY_LIBURLPATTERN_CONSTRUCTOR_STRING_PARSER_H_
#define THIRD_PARTY_LIBURLPATTERN_CONSTRUCTOR_STRING_PARSER_H_

#include <functional>
#include <optional>
#include <string_view>

#include "base/component_export.h"
#include "base/types/expected.h"
#include "third_party/abseil-cpp/absl/status/status.h"
#include "third_party/liburlpattern/tokenize.h"

namespace liburlpattern {

// A helper class to parse the first string passed to the URLPattern
// constructor.  In general the parser works by using the liburlpattern
// tokenizer to first split up the input into pattern tokens.  It can
// then look through the tokens to find non-special characters that match
// the different URL component separators.  Each component is then split
// off and stored in a `Result` object that can be accessed via `GetResult()`.
// The intent is that this object should then be processed as if it was passed
// into the constructor itself.
class COMPONENT_EXPORT(LIBURLPATTERN) ConstructorStringParser {
 public:
  struct Result {
    std::optional<std::string_view> protocol;
    std::optional<std::string_view> username;
    std::optional<std::string_view> password;
    std::optional<std::string_view> hostname;
    std::optional<std::string_view> port;
    std::optional<std::string_view> pathname;
    std::optional<std::string_view> search;
    std::optional<std::string_view> hash;
  };
  using ProtocolCheckCallback =
      std::function<base::expected<bool, absl::Status>(std::string_view)>;

  explicit ConstructorStringParser(std::string_view constructor_string);

  // Attempt to parse the input string used to construct the Parser object.
  // This method may only be called once.  Retrieve the parse result by calling
  // `GetResult()`.
  // `protocol_matches_special_scheme` is called with a protocol string. It must
  // return whether the protocol component which is compiled from the protocol
  // string matches a special scheme. It is not called for relative pattern
  // string. The protocol component created inside the callback can be reused
  // when creating a URLPattern object.
  absl::Status Parse(ProtocolCheckCallback protocol_matches_special_scheme);

  // Return the parse result.  Should only be called after `Parse()` succeeds.
  const Result& GetResult() const { return result_; }

 private:
  enum class StringParseState {
    kInit,
    kProtocol,
    kAuthority,
    kUsername,
    kPassword,
    kHostname,
    kPort,
    kPathname,
    kSearch,
    kHash,
    kDone,
  };

  using Skip = int;

  // A utility function to move from the current `state_` to `new_state`.  This
  // method will populate the component string in `result_` corresponding to the
  // current `state_` automatically.  It will also set `component_start_` and
  // `token_index_` to point to the first token of the next section based on how
  // many tokens the `skip` argument indicates should be ignored.
  void ChangeState(StringParseState new_state, Skip skip);

  // A utility function to move to `new_state`.  This is like `ChangeState()`,
  // but does not automatically set the component string for the current state.
  void ChangeStateWithoutSettingComponent(StringParseState new_state,
                                          Skip skip);

  // Rewind the `token_index_` back to the current `component_start_`.
  void Rewind();

  // Like `Rewind()`, but also sets the state.  This is used for cases where
  // the parser needs to "look ahead" to determine what parse state to enter.
  void RewindAndSetState(StringParseState new_state);

  // Attempt to access the Token at the given `index`.  If the `index` is out
  // of bounds for the `token_list_`, then the last Token in the list is
  // returned.  This will always be a `TokenType::kEnd` token.
  const Token& SafeToken(size_t index) const;

  // Returns true if the token at the given `index` is not a special pattern
  // character and if it matches the given `value`.  This simply checks that the
  // token type is kChar, kEscapedChar, or kInvalidChar.
  bool IsNonSpecialPatternChar(size_t index, const char* value) const;

  // Returns true if the token at the given `index` is the protocol component
  // suffix; e.g. ':'.
  bool IsProtocolSuffix() const;

  // Returns true if the next two tokens are slashes; e.g. `//`.
  bool NextIsAuthoritySlashes() const;

  // Returns true if the tokan at the given `index` is the `@` character used
  // to separate username and password from the hostname.
  bool IsIdentityTerminator() const;

  // Returns true if the current token is the password prefix; e.g. `:`.
  bool IsPasswordPrefix() const;

  // Returns true if the current token is the port prefix; e.g. `:`.
  bool IsPortPrefix() const;

  // Returns true if the current token is the start of the pathname; e.g. `/`.
  bool IsPathnameStart() const;

  // Returns true if the current token is the search component prefix; e.g. `?`.
  // This also takes into account if this could be a valid pattern modifier by
  // looking at the preceding tokens.
  bool IsSearchPrefix() const;

  // Returns true if the current token is the hsah component prefix; e.g. `#`.
  bool IsHashPrefix() const;

  // These methods indicate if the current token is opening or closing a pattern
  // grouping; e.g. `{` or `}`.
  bool IsGroupOpen() const;
  bool IsGroupClose() const;

  // These methods indicate if the current token is an opening or closing
  // bracket for an ipv6 hostname; e.g. '[' or ']'.
  bool IsIPv6Open() const;
  bool IsIPv6Close() const;

  // This method returns a std::string_view consisting of the tokens between
  // `component_start_` and the current `token_index_`.
  std::string_view MakeComponentString() const;

  // The input UTF-8 string to the parser.
  const std::string_view input_;

  // The list of Tokens produced by calling `Tokenize()` on `input_`.
  std::vector<Token> token_list_;

  // As we parse the input string we populate a `URLPatternInit` dictionary
  // with each component pattern.  This is then the final result of the parse.
  Result result_;

  // The index of the first Token to include in the component string.
  size_t component_start_ = 0;

  // The index of the current Token being considered.
  size_t token_index_ = 0;

  // The value to add to `token_index_` on each turn the through the parse
  // loop.  While typically this is `1`, it is also set to `0` at times for
  // things like state transitions, etc.  It is automatically reset back to
  // `1` at the top of the parse loop.
  size_t token_increment_ = 1;

  // The current nesting depth of `{ }` pattern groupings.
  int group_depth_ = 0;

  // The current netsting depth of `[ ]` in hostname patterns.
  int hostname_ipv6_bracket_depth_ = 0;

  // The current parse state.  This should only be changed via `ChangeState()`
  // or `RewindAndSetState()`.
  StringParseState state_ = StringParseState::kInit;

  // True if we should apply parse rules as if this is a "standard" URL.  If
  // false then this is treated as a "not a base URL" or "path" URL.
  bool should_treat_as_standard_url_ = false;
};

}  // namespace liburlpattern

#endif  // THIRD_PARTY_LIBURLPATTERN_CONSTRUCTOR_STRING_PARSER_H_
