# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
  module Lexers
    class Jinja < TemplateLexer
      title "Jinja"
      desc "Django/Jinja template engine (jinja.pocoo.org)"

      tag 'jinja'
      aliases 'django'

      mimetypes 'application/x-django-templating', 'application/x-jinja',
                'text/html+django', 'text/html+jinja'

      def self.keywords
        @@keywords ||= %w(as context do else extends from ignore missing
                          import include reversed recursive scoped
                          autoescape endautoescape block endblock call endcall
                          filter endfilter for endfor if endif macro endmacro
                          set endset trans endtrans with endwith without)
      end

      def self.tests
        @@tests ||= %w(callable defined divisibleby equalto escaped even iterable
                       lower mapping none number odd sameas sequence string
                       undefined upper)
      end

      def self.pseudo_keywords
        @@pseudo_keywords ||= %w(true false none True False None)
      end

      def self.word_operators
        @@word_operators ||= %w(is in and or not)
      end

      state :root do
        # Comments
        rule %r/{#/, Comment, :comment

        # Statements
        rule %r/\{\%/ do
          token Comment::Preproc
          push :statement
        end

        # Expressions
        rule %r/\{\{/ do
          token Comment::Preproc
          push :expression
        end

        rule(/(.+?)(?=\\|{{|{%|{#)/m) { delegate parent }
        rule(/.+/m) { delegate parent }
      end

      state :filter do
        # Filters are called like variable|foo(arg1, ...)
        rule %r/(\|)(\w+)/ do
          groups Operator, Name::Function
        end
      end

      state :function do
        rule %r/(\w+)(\()/ do
          groups Name::Function, Punctuation
        end
      end

      state :text do
        rule %r/\s+/m, Text
      end

      state :literal do
        # Strings
        rule %r/"(\\.|.)*?"/, Str::Double
        rule %r/'(\\.|.)*?'/, Str::Single

        # Numbers
        rule %r/\d+(?=}\s)/, Num

        # Arithmetic operators (+, -, *, **, //, /)
        # TODO : implement modulo (%)
        rule %r/(\+|\-|\*|\/\/?|\*\*?|=)/, Operator

        # Comparisons operators (<=, <, >=, >, ==, ===, !=)
        rule %r/(<=?|>=?|===?|!=)/, Operator

        # Punctuation (the comma, [], ())
        rule %r/,/,  Punctuation
        rule %r/\[/, Punctuation
        rule %r/\]/, Punctuation
        rule %r/\(/, Punctuation
        rule %r/\)/, Punctuation
      end

      state :comment do
        rule(/[^{#]+/m) { token Comment }
        rule(/#}/) { token Comment; pop! }
      end

      state :expression do
        rule %r/\w+\.?/m, Name::Variable

        mixin :filter
        mixin :function
        mixin :literal
        mixin :text

        rule %r/%}|}}/, Comment::Preproc, :pop!
      end

      state :statement do
        rule %r/(raw|verbatim)(\s+)(\%\})/ do
          groups Keyword, Text, Comment::Preproc
          goto :raw
        end

        rule %r/(\w+\.?)/ do |m|
          if self.class.keywords.include?(m[0])
            groups Keyword
          elsif self.class.pseudo_keywords.include?(m[0])
            groups Keyword::Pseudo
          elsif self.class.word_operators.include?(m[0])
            groups Operator::Word
          elsif self.class.tests.include?(m[0])
            groups Name::Builtin
          else
            groups Name::Variable
          end
        end

        mixin :filter
        mixin :function
        mixin :literal
        mixin :text

        rule %r/\%\}/, Comment::Preproc, :pop!
      end

      state :raw do
        rule %r{(\{\%)(\s+)(endverbatim|endraw)(\s+)(\%\})} do
          groups Comment::Preproc, Text, Keyword, Text, Comment::Preproc
          pop!
        end

        rule %r/(.+?)/m, Text
      end
    end
  end
end
