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

module Rouge
  module Lexers
    load_lexer 'c.rb'

    class ObjectiveC < C
      tag 'objective_c'
      title "Objective-C"
      desc 'an extension of C commonly used to write Apple software'
      aliases 'objc', 'obj-c', 'obj_c', 'objectivec'
      filenames '*.m', '*.h'

      mimetypes 'text/x-objective_c', 'application/x-objective_c'

      def self.at_keywords
        @at_keywords ||= %w(
          selector private protected public encode synchronized try
          throw catch finally end property synthesize dynamic optional
          interface implementation import
        )
      end

      def self.at_builtins
        @at_builtins ||= %w(true false YES NO)
      end

      def self.builtins
        @builtins ||= %w(YES NO nil)
      end

      id = /[a-z$_][a-z0-9$_]*/i

      prepend :statements do
        rule /@"/, Str, :string
        rule /@'(\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|\\.|[^\\'\n]')/,
          Str::Char
        rule /@(\d+[.]\d*|[.]\d+|\d+)e[+-]?\d+l?/i,
          Num::Float
        rule /@(\d+[.]\d*|[.]\d+|\d+f)f?/i, Num::Float
        rule /@0x\h+[lL]?/, Num::Hex
        rule /@0[0-7]+l?/i, Num::Oct
        rule /@\d+l?/, Num::Integer
        rule /\bin\b/, Keyword

        rule /@(?:interface|implementation)\b/ do
          token Keyword
          goto :classname
        end

        rule /@(?:class|protocol)\b/ do
          token Keyword
          goto :forward_classname
        end

        rule /@([[:alnum:]]+)/ do |m|
          if self.class.at_keywords.include? m[1]
            token Keyword
          elsif self.class.at_builtins.include? m[1]
            token Name::Builtin
          else
            token Error
          end
        end

        rule /[?]/, Punctuation, :ternary
        rule /\[/,  Punctuation, :message
        rule /@\[/, Punctuation, :array_literal
        rule /@\{/, Punctuation, :dictionary_literal
      end

      state :ternary do
        rule /:/, Punctuation, :pop!
        mixin :statements
      end

      state :message_shared do
        rule /\]/, Punctuation, :pop!
        rule /\{/, Punctuation, :pop!
        rule /;/, Error

        mixin :statement
      end

      state :message do
        rule /(#{id})(\s*)(:)/ do
          groups(Name::Function, Text, Punctuation)
          goto :message_with_args
        end

        rule /(#{id})(\s*)(\])/ do
          groups(Name::Function, Text, Punctuation)
          pop!
        end

        mixin :message_shared
      end

      state :message_with_args do
        rule /\{/, Punctuation, :function
        rule /(#{id})(\s*)(:)/ do
          groups(Name::Function, Text, Punctuation)
          pop!
        end

        mixin :message_shared
      end

      state :array_literal do
        rule /]/, Punctuation, :pop!
        rule /,/, Punctuation
        mixin :statements
      end

      state :dictionary_literal do
        rule /}/, Punctuation, :pop!
        rule /,/, Punctuation
        mixin :statements
      end

      state :classname do
        mixin :whitespace

        rule /(#{id})(\s*)(:)(\s*)(#{id})/ do
          groups(Name::Class, Text,
                 Punctuation, Text,
                 Name::Class)
          pop!
        end

        rule /(#{id})(\s*)([(])(\s*)(#{id})(\s*)([)])/ do
          groups(Name::Class, Text,
                 Punctuation, Text,
                 Name::Label, Text,
                 Punctuation)
          pop!
        end

        rule id, Name::Class, :pop!
      end

      state :forward_classname do
        mixin :whitespace

        rule /(#{id})(\s*)(,)(\s*)/ do
          groups(Name::Class, Text, Punctuation, Text)
          push
        end

        rule /(#{id})(\s*)(;?)/ do
          groups(Name::Class, Text, Punctuation)
          pop!
        end
      end

      prepend :root do
        rule %r(
          ([-+])(\s*)
          ([(].*?[)])?(\s*)
          (?=#{id}:?)
        )ix do |m|
          token Keyword, m[1]; token Text, m[2]
          recurse m[3]; token Text, m[4]
          push :method_definition
        end
      end

      state :method_definition do
        rule /,/, Punctuation
        rule /[.][.][.]/, Punctuation
        rule /([(].*?[)])(#{id})/ do |m|
          recurse m[1]; token Name::Variable, m[2]
        end

        rule /(#{id})(\s*)(:)/m do
          groups(Name::Function, Text, Punctuation)
        end

        rule /;/, Punctuation, :pop!

        rule /{/ do
          token Punctuation
          goto :function
        end

        mixin :inline_whitespace
        rule %r(//.*?\n), Comment::Single
        rule /\s+/m, Text

        rule(//) { pop! }
      end
    end
  end
end
