// Copyright (c) 2014-2017 Dr. Colin Hirsch and Daniel Frey
// Please see LICENSE for license or visit https://github.com/ColinH/PEGTL/

#ifndef TAO_CPP_PEGTL_CONTRIB_RAW_STRING_HH
#define TAO_CPP_PEGTL_CONTRIB_RAW_STRING_HH

#include "../action_input.hh"
#include "../apply_mode.hh"
#include "../nothing.hh"

#include "../internal/must.hh"
#include "../internal/until.hh"
#include "../internal/state.hh"
#include "../internal/skip_control.hh"

#include "../analysis/generic.hh"

namespace tao_json_pegtl
{
   namespace internal
   {
      template< char Open, char Intermediate, char Close >
      struct raw_string_tag
      { };

      template< typename Tag >
      struct raw_string_state
      {
         template< typename Input, typename ... States >
         raw_string_state( const Input & in, States && ... )
               : byte( in.byte() ),
                 line( in.line() ),
                 byte_in_line( in.byte_in_line() ),
                 size( in.size( 0 ) )
         { }

         template< apply_mode A, template< typename ... > class Action, template< typename ... > class Control, typename Input, typename ... States >
         typename std::enable_if< ( ( A == apply_mode::ACTION ) && ( ! is_nothing< Action, Tag >::value ) ) >::type
         success( const Input & in, States && ... st ) const
         {
            using eol_t = typename Input::eol_t;
            using action_t = typename Input::action_t;
            const auto * const begin = in.begin() - size + in.size( 0 ) + count;
            action_t action_in( byte, line, byte_in_line, begin + ( ( * begin ) == eol_t::ch ), in.begin() - count, in.source() );
            Action< Tag >::apply( const_cast< const action_t & >( action_in ), st ... );
         }

         template< apply_mode A, template< typename ... > class Action, template< typename ... > class Control, typename Input, typename ... States >
         typename std::enable_if< ! ( ( A == apply_mode::ACTION ) && ( ! is_nothing< Action, Tag >::value ) ) >::type
         success( const Input &, States && ... ) const
         { }

         raw_string_state( const raw_string_state & ) = delete;
         void operator= ( const raw_string_state & ) = delete;

         std::size_t byte;
         std::size_t line;
         std::size_t byte_in_line;
         std::size_t size;
         std::size_t count = 0;
      };

      template< typename Tag, char Open, char Intermediate >
      struct raw_string_open
      {
         using analyze_t = analysis::generic< analysis::rule_type::ANY >;

         template< apply_mode A, template< typename ... > class Action, template< typename ... > class Control, typename Input >
         static bool match( Input & in, raw_string_state< Tag > & ls )
         {
            if ( in.empty() || ( in.peek_char( 0 ) != Open ) ) {
               return false;
            }
            for ( std::size_t i = 1; i < in.size( i + 1 ); ++i ) {
               switch ( const auto c = in.peek_char( i ) ) {
                  case Open:
                     ls.count = i + 1;
                     in.bump( ls.count );
                     return true;
                  case Intermediate:
                     break;
                  default:
                     return false;
               }
            }
            return false;
         }
      };

      template< typename Tag, char Intermediate, char Close >
      struct raw_string_close
      {
         using analyze_t = analysis::generic< analysis::rule_type::ANY >;

         template< apply_mode A, template< typename ... > class Action, template< typename ... > class Control, typename Input >
         static bool match( Input & in, const raw_string_state< Tag > & ls )
         {
            if ( in.size( ls.count ) < ls.count ) {
               return false;
            }
            if ( in.peek_char( 0 ) != Close ) {
               return false;
            }
            if ( in.peek_char( ls.count - 1 ) != Close ) {
               return false;
            }
            for ( std::size_t i = 0; i < ls.count - 2; ++i ) {
               if ( in.peek_char( i + 1 ) != Intermediate ) {
                  return false;
               }
            }
            in.bump( ls.count );
            return true;
         }
      };

      template< typename Tag, char Open, char Intermediate >
      struct skip_control< raw_string_open< Tag, Open, Intermediate > > : std::true_type {};

      template< typename Tag, char Intermediate, char Close >
      struct skip_control< raw_string_close< Tag, Intermediate, Close > > : std::true_type {};

   } // namespace internal

   // raw_string matches Lua-style long literals.
   //
   // The following description was taken from the Lua documentation
   // (see http://www.lua.org/docs.html):
   //
   // - An "opening long bracket of level n" is defined as an opening square
   //   bracket followed by n equal signs followed by another opening square
   //   bracket. So, an opening long bracket of level 0 is written as `[[`,
   //   an opening long bracket of level 1 is written as `[=[`, and so on.
   // - A "closing long bracket" is defined similarly; for instance, a closing
   //   long bracket of level 4 is written as `]====]`.
   // - A "long literal" starts with an opening long bracket of any level and
   //   ends at the first closing long bracket of the same level. It can
   //   contain any text except a closing bracket of the same level.
   // - Literals in this bracketed form can run for several lines, do not
   //   interpret any escape sequences, and ignore long brackets of any other
   //   level.
   // - For convenience, when the opening long bracket is immediately followed
   //   by a newline, the newline is not included in the string.
   //
   // Note that unlike Lua's long literal, a raw_string is customizable to use
   // other characters than `[`, `=` and `]` for matching. Also note that Lua
   // introduced newline-specific replacements in Lua 5.2, which we do not
   // support on the grammar level.

   template< char Open, char Intermediate, char Close, typename Tag = internal::raw_string_tag< Open, Intermediate, Close > >
   struct raw_string : internal::state< internal::raw_string_state< Tag >,
                                        internal::raw_string_open< Tag, Open, Intermediate >,
                                        internal::must< internal::until< internal::raw_string_close< Tag, Intermediate, Close > > > >
   {
      // This is used to bind an action to the content.
      using content = Tag;

      // This is used for error-reporting when a raw string is not closed properly.
      using close = internal::until< internal::raw_string_close< Tag, Intermediate, Close > >;
   };

} // namespace tao_json_pegtl

#endif
