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

module Rouge
  module Lexers
    class Brightscript < RegexLexer
      title "BrightScript"
      desc "BrightScript Programming Language (https://developer.roku.com/en-ca/docs/references/brightscript/language/brightscript-language-reference.md)"
      tag 'brightscript'
      aliases 'bs', 'brs'
      filenames '*.brs'

      # https://developer.roku.com/en-ca/docs/references/brightscript/language/global-utility-functions.md
      # https://developer.roku.com/en-ca/docs/references/brightscript/language/global-string-functions.md
      # https://developer.roku.com/en-ca/docs/references/brightscript/language/global-math-functions.md
      def self.name_builtin
        @name_builtin ||= Set.new %w(
          ABS ASC ATN CDBL CHR CINT CONTROL COPYFILE COS CREATEDIRECTORY CSNG
          DELETEDIRECTORY DELETEFILE EXP FINDMEMBERFUNCTION FINDNODE FIX
          FORMATDRIVEFORMATJSON GETINTERFACE INSTR INT LCASE LEFT LEN LISTDIR
          LOG MATCHFILES MID MOVEFILE OBSERVEFIELD PARSEJSON PARSEXML
          READASCIIFILE REBOOTSYSTEM RIGHT RND RUNGARBAGECOLLECTOR SGN SIN
          SLEEP SQR STR STRI STRING STRINGI STRTOI SUBSTITUTE TANTEXTTOP TEXT
          TRUCASE UPTIME VALVISIBLE VISIBLE WAIT
        )
      end

      # https://developer.roku.com/en-ca/docs/references/brightscript/language/reserved-words.md
      def self.keyword_reserved
        @keyword_reserved ||= Set.new %w(
          BOX CREATEOBJECT DIM EACH ELSE ELSEIF END ENDFUNCTION ENDIF ENDSUB
          ENDWHILE EVAL EXIT EXITWHILE FALSE FOR FUNCTION GETGLOBALAA
          GETLASTRUNCOMPILEERROR GETLASTRUNRUNTIMEERROR GOTO IF IN INVALID LET
          LINE_NUM M NEXT OBJFUN POS PRINT REM RETURN RUN STEP STOP SUB TAB TO
          TRUE TYPE WHILE
        )
      end

      # These keywords are present in BrightScript, but not supported in standard .brs files
      def self.keyword_reserved_unsupported
        @keyword_reserved_unsupported ||= Set.new %w(
          CLASS CONST IMPORT LIBRARY NAMESPACE PRIVATE PROTECTED PUBLIC
        )
      end

      # https://developer.roku.com/en-ca/docs/references/brightscript/language/expressions-variables-types.md
      def self.keyword_type
        @keyword_type ||= Set.new %w(
          BOOLEAN DIM DOUBLE DYNAMIC FLOAT FUNCTION INTEGER INTERFACE INVALID
          LONGINTEGER OBJECT STRING VOID
        )
      end

      # https://developer.roku.com/en-ca/docs/references/brightscript/language/expressions-variables-types.md#operators
      def self.operator_word
        @operator_word ||= Set.new %w(
          AND AS MOD NOT OR THEN
        )
      end

      # Scene graph components configured as builtins. See BrightScript component documentation e.g.
      # https://developer.roku.com/en-ca/docs/references/brightscript/components/roappinfo.md
      def self.builtins
        @builtins ||= Set.new %w(
          roAppendFile roAppInfo roAppManager roArray roAssociativeArray
          roAudioGuide roAudioMetadata roAudioPlayer roAudioPlayerEvent
          roAudioResourceroBitmap roBoolean roBrightPackage roBrSub
          roButton roByteArray roCaptionRenderer roCaptionRendererEvent
          roCecInterface roCECStatusEvent roChannelStore roChannelStoreEvent
          roClockWidget roCodeRegistrationScreen
          roCodeRegistrationScreenEventroCompositor roControlDown roControlPort roControlUp roCreateFile roDatagramReceiver
          roDatagramSender roDataGramSocket roDateTime roDeviceInfo
          roDeviceInfoEvent roDoubleroEVPCipher roEVPDigest roFileSystem
          roFileSystemEvent roFloat roFont roFontMetrics roFontRegistry
          roFunction roGlobal roGpio roGridScreen roGridScreenEvent
          roHdmiHotPlugEventroHdmiStatus roHdmiStatusEvent roHMAC roHttpAgent
          roImageCanvas roImageCanvasEvent roImageMetadata roImagePlayer
          roImageWidgetroInput roInputEvent roInt roInvalid
          roIRRemote roKeyboard roKeyboardPress roKeyboardScreen
          roKeyboardScreenEventroList roListScreen roListScreenEvent
          roLocalization roLongInteger roMessageDialog roMessageDialogEvent
          roMessagePort roMicrophone roMicrophoneEvent roNetworkConfiguration
          roOneLineDialog roOneLineDialogEventroParagraphScreen
          roParagraphScreenEvent roPath roPinEntryDialog roPinEntryDialogEvent
          roPinentryScreen roPosterScreen roPosterScreenEventroProgramGuide
          roQuadravoxButton roReadFile roRectangleroRegexroRegion roRegistry
          roRegistrySection roResourceManager roRSA roRssArticle roRssParser
          roScreen roSearchHistory roSearchScreen roSearchScreenEvent
          roSerialPort roSGNode roSGNodeEvent roSGScreenroSGScreenEvent
          roSlideShowroSlideShowEvent roSNS5 roSocketAddress roSocketEvent
          roSpringboardScreen roSpringboardScreenEventroSprite roStorageInfo
          roStreamSocket roStringroSystemLogroSystemLogEvent roSystemTime
          roTextFieldroTextScreen roTextScreenEvent roTextToSpeech
          roTextToSpeechEvent roTextureManager roTextureRequest
          roTextureRequestEventroTextWidget roTimer roTimespan roTouchScreen
          roTunerroTunerEvent roUniversalControlEvent roUrlEvent roUrlTransfer
          roVideoEvent roVideoInput roVideoMode roVideoPlayer roVideoPlayerEvent
          roVideoScreen roVideoScreenEventroWriteFile roXMLElement roXMLList
        )
      end

      id = /[$a-z_][a-z0-9_]*/io

      state :root do
        rule %r/\s+/m, Text::Whitespace

        # https://developer.roku.com/en-ca/docs/references/brightscript/language/expressions-variables-types.md#comments
        rule %r/\'.*/, Comment::Single
        rule %r/REM.*/i, Comment::Single

        # https://developer.roku.com/en-ca/docs/references/brightscript/language/expressions-variables-types.md#operators
        rule %r([~!%^&*+=\|?:<>/-]), Operator

        rule %r/\d*\.\d+(e-?\d+)?/i, Num::Float
        rule %r/\d+[lu]*/i, Num::Integer

        rule %r/".*?"/, Str::Double

        rule %r/#{id}(?=\s*[(])/, Name::Function

        rule %r/[()\[\],.;{}]/, Punctuation

        rule id do |m|
          caseSensitiveChunk = m[0]
          caseInsensitiveChunk = m[0].upcase

          if self.class.builtins.include?(caseSensitiveChunk)
            token Keyword::Reserved
          elsif self.class.keyword_reserved.include?(caseInsensitiveChunk)
            token Keyword::Reserved
          elsif self.class.keyword_reserved_unsupported.include?(caseInsensitiveChunk)
            token Keyword::Reserved
          elsif self.class.keyword_type.include?(caseInsensitiveChunk)
            token Keyword::Type
          elsif self.class.name_builtin.include?(caseInsensitiveChunk)
            token Name::Builtin
          elsif self.class.operator_word.include?(caseInsensitiveChunk)
            token Operator::Word
          else
            token Name
          end
        end
      end
    end
  end
end
