package ulsp

import json
link regexp
link ximage

global begins, ends

class HoverHandler(
   filehandler,
   params
   )

   method setVariables(new_filehandler, new_params)
      filehandler := new_filehandler
      params := new_params
   end

   method getResult(lsif)
      local line, character, desired_line, hover_item, signatureHandler, sig_table, results_table, _context, context
      if /filehandler then fail
      results_table := table()

      line := params["position"]["line"] + 1
      character := params["position"]["character"] + 1

      context := filehandler.getContext(line, character) | fail

      desired_line := context.line
      character := context.charNum

      if /desired_line | /character then fail

      signatureHandler := SignatureHandler()
      signatureHandler.setVariables(filehandler)

      hover_item := \getHoverItem(desired_line, character) | return results_table
      _context := \getContext(context) | return results_table

      if _context == "function" | _context == "built-in-function" | _context == "method" |
         _context == "procedure" | _context == "constructor" then {
               sig_table := signatureHandler.handleNewSignature(context)
               hover_item := sig_table[1]["label"]
      }

      if \sig_table & member(sig_table[1], "documentation") then {
         results_table["contents"] := table("kind", "markdown", "value", "(_" || _context || "_) " ||
               hover_item || "\n \n --- \n \n" || sig_table[1]["documentation"]["value"])
      }
      else {
         results_table["contents"] := table("kind", "markdown", "value", "(_" || _context || "_) " || hover_item )
      }

      if \lsif then {
         if \sig_table & member(sig_table[1], "documentation") then {
            return table(
               "item", "(_" || _context || "_) " || hover_item || "\n \n --- \n \n" || sig_table[1]["documentation"]["value"],
               "begins", begins,
               "ends", ends,
               "line", line)
         } else {
            return table(
               "item", "(_" || _context || "_) " || hover_item,
               "begins", begins,
               "ends", ends,
               "line", line)
         }
      }
      return results_table
   end

   method getContext(context)
      local line_num, procedure_name, method_name, class_name, single_quote, double_quote, ch, _item, c, desired_line,
      num_params, param, num_attributes, _attribute, line, character, begin, ends

      line := context.line
      \line | fail
      character := context.charNum

      # Begin initial string scanning
      line ? {

         # Check if position is within a comment or quoted text
         single_quote := 0
         double_quote := 0
         while (&pos < character) do {
            ch := move(1) | break
            if (ch == "\'" & (line[&pos-2] ~== "\\")) then single_quote +:= 1
            if (ch == "\"" & (line[&pos-2] ~== "\\")) then double_quote +:= 1
            if (ch == "#") & ((single_quote % 2) == 0 & (double_quote % 2) == 0) then fail
         }
         if (single_quote % 2) ~== 0 | (double_quote % 2) ~== 0 then fail

         # If item is a function then find the type
         &pos := character
         tab(many(identifiers))
         if line[&pos] == "(" then {
            _item := getHoverItem(line, character)

            # If item has . before then return method
            while c := move(-1) do {
               if (c ** identifiers) ~== c then {
                  break
               }
            }
            if line[&pos] == "." then {
               return "method"
            }

            # If item is method of the class it's inside of then return method
            line_num := params["position"]["line"] + 1

            context := filehandler.getContext(line_num, character)
            desired_line := context.line
            character := context.charNum

            until ReMatch("^[\ \t]*class[\ \t]+", desired_line) |
               ReMatch("^[\ \t]*procedure[\ \t]+", desired_line) | line_num = 0 do {

               context := filehandler.getContext(line_num, character)
               desired_line := context.line
               character := context.charNum

               if ReMatch("^[\ \t]*class[\ \t]+", desired_line) then {
                  desired_line ? {
                     tab(many(' \t'))
                     move(5)
                     tab(many(' \t'))
                     class_name := tab(many(identifiers))
                  }

                  if member(filehandler.internal_classes[class_name]["methods"], _item) then
                     return "method"
               }
               line_num -:= 1
            }

            # If item is an associated class then return constructor
            if member(filehandler.linked_classes, _item) | member(filehandler.imported_classes, _item) |
               member(filehandler.internal_classes, _item) | member(filehandler.package_classes, _item) then
                  return "constructor"

            # If item is an associated procedure then return procedure
            if member(filehandler.linked_procedures, _item) | member(filehandler.imported_procedures, _item) |
               member(filehandler.internal_procedures, _item) | member(filehandler.package_procedures, _item) then
                  return "procedure"

            # If item is a built-in-function then return built-in-function
            if member(filehandler.getBuiltInFunctions(), _item) then
               return "built-in-function"

            # If all else fails
            return "unknown"
         }
      }

      line_num := params["position"]["line"] + 1

      # Process for determining if item is parameter
      until line_num = 1 do {
         context := filehandler.getContext(line_num, character)
         desired_line := context.line
         character := context.charNum

         # If item is param of next closest procedure then return param
         if ReMatch("^[\ \t]*procedure[\ \t]+", desired_line) then {
            desired_line ? {
               tab(many(' \t'))
               move(9)
               tab(many(' \t'))
               procedure_name := tab(many(identifiers))
            }

            if member(filehandler.internal_procedures, procedure_name) then {
               num_params := *filehandler.internal_procedures[procedure_name]["params"]
               every param := filehandler.internal_procedures[procedure_name]["params"][1 to num_params] do {
                  if param["name"] == getHoverItem(line, character) then return "parameter"
               }
            }
            break
         }

         # If item is param of next closest method then return param
         else if ReMatch("^[\ \t]*method[\ \t]+", desired_line) then {
            desired_line ? {
               tab(many(' \t'))
               move(6)
               tab(many(' \t'))
               method_name := tab(many(identifiers))
            }
            line_num -:= 1
            until line_num = 1 do {

               context := filehandler.getContext(line_num, character)
               desired_line := context.line
               character := context.charNum

               if ReMatch("^[\ \t]*class[\ \t]+", desired_line) then {
                  desired_line ? {
                     tab(many(' \t'))
                     move(5)
                     tab(many(' \t'))
                     class_name := tab(many(identifiers))
                  }
                  break
               }
               line_num -:= 1
            }

            if member(filehandler.internal_classes, class_name) & member(filehandler.internal_classes[class_name]["methods"], method_name) then {
               num_params := *filehandler.internal_classes[class_name]["methods"][method_name]["params"]
               every param := filehandler.internal_classes[class_name]["methods"][method_name]["params"][1 to num_params] do {
                  if param["name"] == getHoverItem(line, character) then return "parameter"
               }
            }

            # If item is an attribute (class parameter) then return class parameter
            if member(filehandler.internal_classes, class_name) then {
               num_attributes := *filehandler.internal_classes[class_name]["attributes"]
               every _attribute := filehandler.internal_classes[class_name]["attributes"][1 to num_attributes] do {
                  if _attribute["name"] == getHoverItem(line, character) then return "class parameter"
               }
            }
            break
         }

         else if ReMatch("^[\ \t]*class[\ \t]+", desired_line) then {
            desired_line ? {
               tab(many(' \t'))
               move(5)
               tab(many(' \t'))
               class_name := tab(many(identifiers))
            }

            if member(filehandler.internal_classes, class_name) then {
               num_attributes := *filehandler.internal_classes[class_name]["attributes"]
               every _attribute := filehandler.internal_classes[class_name]["attributes"][1 to num_attributes] do {
                  if _attribute["name"] == getHoverItem(line, character) then return "class parameter"
               }
            }
         }
         line_num -:= 1
      }

      line_num := params["position"]["line"] + 1

      # If item is a member of global variables then return global
      if member(filehandler.global_variables, getHoverItem(line, character)) then return "global"

      # If item is an associated variable or object then return variable or object respectively
      if member(filehandler.objects, getHoverItem(line, character)) then return "object"
      if member(filehandler.variables, getHoverItem(line, character)) then return "variable"
   end

   method getHoverItem(line, character)
      local c, b
      if (line[character] ** identifiers) ~== line[character] then fail

      line ? {
         &pos := character

         while c := move(-1) do {
            if (c ** identifiers) ~== c then break
         }

         if &pos ~= 1 then move(1)
         begins := &pos
         b := tab(many(identifiers))
         ends := &pos
         return b
      }
   end
end