# -------------------------------------------------------------------------
# ---------------------- FileHandler Class --------------------------------
# -------------------------------------------------------------------------
#
# Description:
#    -Contains all items related to a particular opened filehandler file.
#    -This includes:
#       -The uri of the file
#       -The contents of the file (synchronized contents coming from client - updating in textDocument/didChange)
#       -links used in the file.
#       -imports used in the file.
#       -Classes (and their methods, attributes, etc.) available from links/imports
#       -Procedures available from links/imports
#       -Classes (and their methods, attributes, etc.) created within the file.
#       -Procedures created within the file.
#       -Objects instantiated within the file.
#

package ulsp

link regexp
link io
import json
import UniDoc

import lang
import util
import parser

global identifiers

class FileHandler(
   uri,
   lsp_database,
   temp_file,
   #   file_contents,
   links,
   imports,
   _package,
   objects,
   variables,
   global_variables,
   internal_procedures,
   internal_classes,
   linked_procedures,
   package_procedures,
   linked_classes,
   imported_procedures,
   imported_classes,
   package_classes,
   m_uni_file,
   temp_idoc,
   context,
   internal_positions
   )

   method updateFileHandler()
      setLinks()
      setImports()
      setPackage()
      setInternalProcedures()
      setLinkedProcedures()
      setImportedProcedures()
      setPackageProcedures()
      setInternalClasses()
      setLinkedClasses()
      setImportedClasses()
      setPackageClasses()
      setObjects()
      setVariables()
      setInternalPositions()
   end

   method setFile()
      local files

      temp_idoc := UniAll()
      temp_idoc.setSourcePath(uri)
      temp_idoc.processFile(uri)

      files := temp_idoc.getAllFiles()
      m_uni_file := files[1]
   end

   method getFile()
      if \m_uni_file then
         return m_uni_file
   end

   method updateUniDoc(uri, contents)
      local files, file_contents
      temp_idoc.processFile(uri, contents)
      file_contents := contents
      files := temp_idoc.getAllFiles()
      m_uni_file := files[1]
   end

   method deleteTempFile()
      remove(temp_file)
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Links (get, set, update) -------------------------------------------------------
# -------------------------------------------------------------------------------------------------


   method setLinks()
      local links_w_ext, link_w_ext, temp, _link, pack
      links := []
      getFile() | fail
      links_w_ext := getFile().getLinkNames() | fail
      every link_w_ext := !links_w_ext do {
         temp := reverse(link_w_ext)
         temp ? {
            move(4)
            _link := tab(0)
            _link := reverse(_link)
         }
         every pack := key(lsp_database.package_db) do {
            if member(lsp_database.package_db[pack]["files"], _link) then
               put(links, _link)
         }
      }
   end

   method getLinks()
      return links
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Imports (get, set, update) -----------------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setImports()
      local _import
      imports := []
      getFile() | fail
      every _import := !getFile().getImportNames() do {
         if member(lsp_database.package_db, _import) then
            put(imports, _import)
      }
   end

   method getImports()
      return imports
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Package (get, set, update) -----------------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setPackage()
      local file_loc
      getFile() | fail
      if \getFile().getPackageName() then {
         _package := getFile().getPackageName()
         if not member(lsp_database.package_db, _package) then {
            uri ? {
               &pos := 0
               until uri[&pos] == "\/" | &pos = 1 do move(-1)
               file_loc := tab(1)
            }
            lsp_database.build_by_path(file_loc)
         }
      }
   end

    method getPackage()
       return _package
    end

# -------------------------------------------------------------------------------------------------
# ---------------- Internal Procedures (get, set, update) -----------------------------------------
# -------------------------------------------------------------------------------------------------

   method setInternalProcedures()
      local _procedure, _param, paramName, paramType, paramDef
      internal_procedures := table()
      getFile() | fail
      every _procedure := getFile().getProcedures().get() do {
         _procedure.setSrcFile(uri)
         internal_procedures[_procedure.getName()] := table()
         internal_procedures[_procedure.getName()]["comments"] := makeComment(_procedure)

         internal_procedures[_procedure.getName()]["params"] := []
         every _param := _procedure.getParams().get() do {
            paramName := _param.getName()
            paramType := _param.getTypeValue() | &null
            paramDef := _param.getDefValue() | &null
            put(internal_procedures[_procedure.getName()]["params"], table(
               "name", paramName,
               "type", paramType,
               "default", paramDef))
         }

         internal_procedures[_procedure.getName()]["source"] := uri
      }
   end


   method getInternalProcedures()
      return internal_procedures
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Linked Procedures (get, set, update) -------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setLinkedProcedures()
      local _link, _key, _package
      linked_procedures := table()
      every _link := !links do {
         if _link ~=== "class" then {
            every _package := key(lsp_database.package_db) do {
               if member(lsp_database.package_db[_package]["files"], _link) then {
                  if member(lsp_database.package_db[_package]["files"][_link], "procedures") then {
                     every _key := key(lsp_database.package_db[_package]["files"][_link]["procedures"]) do {
                        linked_procedures[_key] := lsp_database.package_db[_package]["files"][_link]["procedures"][_key]
                     }
                  }
               }
            }
         }
      }
   end

   method getLinkedProcedures()
      return linked_procedures
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Imported Procedures (get, set, update) -----------------------------------------
# -------------------------------------------------------------------------------------------------

   method setImportedProcedures()
      local _import, file_in_pack, _key
      imported_procedures := table()
      every _import := !imports do {
         every file_in_pack := !lsp_database.package_db[_import]["files"] do {
            every _key := key(file_in_pack["procedures"]) do {
               imported_procedures[_key] := file_in_pack["procedures"][_key]
               }
            }
         }
   end

   method getImportedProcedures()
      return imported_procedures
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Package Procedures (get, set, update) ------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setPackageProcedures()
      local file_in_pack, _key
      package_procedures := table()
      if member(lsp_database.package_db, _package) then {
         every file_in_pack := !lsp_database.package_db[_package]["files"] do {
            every _key := key(file_in_pack["procedures"]) do {
               package_procedures[_key] := file_in_pack["procedures"][_key]
            }
         }
      }
   end

   method getPackageProcedures()
      return package_procedures
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Internal Classes (get, set, update) --------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setInternalClasses()
      local _class, _method, _param, paramName, paramType, paramDef, constructor_param
      internal_classes := table()
      getFile() | fail
      every _class := getFile().getClasses().get() do {
         _class.setSrcFile(uri)
         internal_classes[_class.getName()] := table()
         internal_classes[_class.getName()]["source"] := uri

         internal_classes[_class.getName()]["methods"] := table()
         every _method := _class.getMethods().get() do {
            _method.setSrcFile(uri)
            internal_classes[_class.getName()]["methods"][_method.getName()] := table()
            internal_classes[_class.getName()]["methods"][_method.getName()]["comments"] := makeComment(_method)

            internal_classes[_class.getName()]["methods"][_method.getName()]["params"] := []
            every _param := _method.getParams().get() do {
               paramName := _param.getName()
               paramType := _param.getTypeValue() | &null
               paramDef := _param.getDefValue() | &null
               put(internal_classes[_class.getName()]["methods"][_method.getName()]["params"], table(
                  "name", paramName,
                  "type", paramType,
                  "default", paramDef))
            }

            internal_classes[_class.getName()]["methods"][_method.getName()]["source"] := uri
         }

         internal_classes[_class.getName()]["attributes"] := []
         every _param := _class.getParams().get() do {
            paramName := _param.getName()
            paramType := _param.getTypeValue() | &null
            paramDef := _param.getDefValue() | &null
            put(internal_classes[_class.getName()]["attributes"], table(
               "name", paramName,
               "type", paramType,
               "default", paramDef))
         }

         internal_classes[_class.getName()]["constructor"] := table()
         internal_classes[_class.getName()]["constructor"]["name"] := _class.getConstructor().getName()
         internal_classes[_class.getName()]["constructor"]["comments"] := makeComment(_class)
         internal_classes[_class.getName()]["constructor"]["params"] := []
         every constructor_param := _class.getConstructor().getParams().get() do {
            paramName := constructor_param.getName()
            paramType := constructor_param.getTypeValue() | &null
            paramDef := constructor_param.getDefValue() | &null
            put(internal_classes[_class.getName()]["constructor"]["params"], table(
               "name", paramName,
               "type", paramType,
               "default", paramDef))
         }
      }
   end

   method getInternalClasses()
      return internal_classes
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Linked Classes (get, set, update) ----------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setLinkedClasses()
      local _link, _key, _package
      linked_classes := table()
      every _link := !links do {
         if _link ~=== "class" then {
            every _package := key(lsp_database.package_db) do {
               if member(lsp_database.package_db[_package]["files"], _link) then {
                  if member(lsp_database.package_db[_package]["files"][_link], "classes") then {
                     every _key := key(lsp_database.package_db[_package]["files"][_link]["classes"]) do {
                        linked_procedures[_key] := lsp_database.package_db[_package]["files"][_link]["classes"][_key]
                     }
                  }
               }
            }
         }
      }
   end

   method getLinkedClasses()
      return linked_classes
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Imported Classes (get, set, update) --------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setImportedClasses()
      local _import, file_in_pack, _key
      imported_classes := table()
      every _import := !imports do {
         every file_in_pack := !lsp_database.package_db[_import]["files"] do {
            every _key := key(file_in_pack["classes"]) do {
               imported_classes[_key] := file_in_pack["classes"][_key]
            }
         }
      }
   end

   method getImportedClasses()
      return imported_classes
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Package Classes (get, set, update) ---------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setPackageClasses()
      local file_in_pack, _key
      package_classes := table()
      if member(lsp_database.package_db, _package) then {
         every file_in_pack := !lsp_database.package_db[_package]["files"] do {
            every _key := key(file_in_pack["classes"]) do {
               package_classes[_key] := file_in_pack["classes"][_key]
            }
         }
      }
   end

   method getPackageClasses()
      return package_classes
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Objects (get, set, update) -----------------------------------------------------
# -------------------------------------------------------------------------------------------------
   method setObjects()
      local lineNum, _line, objectName, className
      objects := table()
      lineNum := 1
      while _line := temp_idoc.getUniFileLine(uri, lineNum) do {
         if ReMatch("^[\ \t]*[a-zA-Z0-9_]+[\ \t]*:=[\ \t]*[a-zA-Z0-9_]+[(][a-zA-Z0-9_]*[)][\ \t]*$", _line) then {
            _line ? {
               tab(many(' \t'))
               objectName := tab(many(&digits ++ &letters ++ '-' ++ '_'))
               tab(many(' \t'))
               move(2)
               tab(many(' \t'))
               className := tab(upto("\("))
               if member(linked_classes, className) | member(imported_classes, className) | member(internal_classes, className) | member(package_classes, className) then {
                  objects[objectName] := className
               }
            }
         }
         lineNum +:= 1
      }
   end


   method getObjects()
      return objects
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Variables (get, set, update) ---------------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setVariables()
      local f, _line, variableName, _content, globalName, lineNum
      variables := table()
      global_variables := table()
      lineNum := 1
      while _line := temp_idoc.getUniFileLine(uri, lineNum) do {
         if ReMatch("^[\ \t]*[a-zA-Z0-9_]+[\ \t]*:=", _line) then {
            _line ? {
               tab(many(' \t'))
               variableName := tab(many(identifiers))
               tab(many(' \t'))
               move(2)
               tab(many(' \t'))
               _content := tab(*_line + 1)
               if not member(objects, variableName) then {
                  variables[variableName] := _content
               }
            }
         }
         #if ReMatch("^[\ \t]*local", _line) then {
         #   _line ? {
         #      tab(many(' \t'))
         #      move(5)
         #      tab(many(' \t'))
         #      until &pos >= *_line do {
         #         temp_pos := &pos
         #         if ReMatch("^[\ \t]*[a-zA-Z0-9_]+[\ \t]*:=", tab(upto(,))) then {
         #            &pos := temp_pos
         #            variableName := tab(many(identifiers))
         #            variables[variableName] := "local declare"
         #            tab(upto(,))
         #         }
         #      }
         #   }
         #}
         if ReMatch("^[\ \t]*global[\ \t]+[a-zA-Z0-9_]+", _line) then {
            _line ? {
               tab(many(' \t'))
               move(6)
               tab(many(' \t'))
               globalName := tab(many(identifiers))
               global_variables[globalName] := ""
            }
         }
         lineNum +:= 1
      }
   end

   method getVariables()
      return variables
   end

   method getGlobalVariables()
      return global_variables
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Ranges (get, set) --------------------------------------------------------------
# -------------------------------------------------------------------------------------------------

   method setInternalPositions()
      local lineNum, _line, procedureName, startPos, endPos, className, methodName, variableName,
            open_blocks, ending, currentClass

      internal_positions := [
         "classes" : table();
         "procedures" : table();
         "methods" : table();
         "variables" : table();
         "initially" : table()
      ]
      open_blocks := []

      lineNum := 1
      while _line := temp_idoc.getUniFileLine(uri, lineNum) do {
         # Check for procedures
         every procedureName := key(internal_procedures) do {
            if ReMatch("^\\s*procedure\\s+" || procedureName || "\\s*\\(", _line) then {
               startPos := find(procedureName, _line)
               endPos := startPos + *procedureName - 1
               push(open_blocks, ["procedures", procedureName])
               internal_positions["procedures"][procedureName] := [
                  "type" : "procedure";
                  "range" : [
                     "start" : [lineNum-1, 0];
                     "end" : [lineNum-1, 0]
                  ];
                  "selectionRange" : [
                     "start" : [lineNum-1, startPos];
                     "end" : [lineNum-1, endPos]
                  ]
               ]
            }
         }

         # Check for classes
         every className := key(internal_classes) do {
            if ReMatch("^\\s*class\\s+" || className || ".*\\(", _line) then {
               currentClass := className
               startPos := find(className, _line)
               endPos := startPos + *className - 1
               push(open_blocks, ["classes", className])
               internal_positions["classes"][className] := [
                  "type" : "class";
                  "range" : [
                     "start" : [lineNum-1, 0];
                     "end" : [lineNum-1, 0]
                  ];
                  "selectionRange" : [
                     "start" : [lineNum-1, startPos];
                     "end" : [lineNum-1, endPos]
                  ]
               ]
            }

            # Check for methods
            every methodName := key(internal_classes[className]["methods"]) do {
               if ReMatch("^\\s*method\\s+" || methodName || "\\s*\\(", _line) then {
                  startPos := find(methodName, _line)
                  endPos := startPos + *methodName - 1
                  push(open_blocks, ["methods", methodName])
                  internal_positions["methods"][methodName] := [
                     "type" : "method";
                     "range" : [
                        "start" : [lineNum-1, 0];
                        "end" : [lineNum-1, 0]
                     ];
                     "selectionRange" : [
                        "start" : [lineNum-1, startPos];
                        "end" : [lineNum-1, endPos]
                     ]
                  ]
               }
            }
         }

         # Check for "initially" sections
         if ReMatch("^\\s*initially\\s*(\\(.*\\))?$", _line) then {
            startPos := find("initially", _line)
            endPos := startPos + 9
            push(open_blocks, ["initially", currentClass])
            internal_positions["initially"][currentClass] := [
               "type" : "method";
               "range" : [
                  "start" : [lineNum-1, 0];
                  "end" : [lineNum-1, 0]
               ];
               "selectionRange" : [
                  "start" : [lineNum-1, startPos];
                  "end" : [lineNum-1, endPos]
               ]
            ]
         }

         # Check for keyword "end"
         if ReMatch("^\\s*end\\s*$", _line) then {
            if *open_blocks > 0 then {
               ending := pop(open_blocks)
               if ending[1] == "initially" then {
                  internal_positions[ending[1]][ending[2]]["range"]["end"] := [lineNum-2, *temp_idoc.getUniFileLine(uri, lineNum-1)]
                  ending := pop(open_blocks)
                  internal_positions[ending[1]][ending[2]]["range"]["end"] := [lineNum-1, *_line]
               } else {
                  internal_positions[ending[1]][ending[2]]["range"]["end"] := [lineNum-1, *_line]
               }
            }
         }

         # Check for variables
         every variableName := key(variables) do {
            if ReMatch("^\\s*" || variableName || "\\s*\\:=", _line) then {
               startPos := find(variableName, _line)
               endPos := startPos + *variableName - 1
               internal_positions["variables"][variableName] := table(
                  "type", "variable",
                  "selectionRange", table(
                     "start", [lineNum-1, startPos-1],
                     "end", [lineNum-1, endPos]
                     )
                  )
            }
         }
         lineNum +:= 1
      }
   end

   method getInternalPositions()
      return internal_positions
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Database accessors -------------------------------------------------------------
# -------------------------------------------------------------------------------------------------


   method getDeclarativeExpressions()
      return lsp_database.declarative_expressions
   end

   method getBuiltInFunctions()
      return lsp_database.built_in_functions
   end

   method getActionWords()
      return lsp_database.action_words
   end

   method getSyntaxReservedWords()
      return lsp_database.syntax_reserved_words
   end

   method getKeywords()
      return lsp_database.keywords
   end

   method getPreprocessors()
      return lsp_database.preprocessors
   end

# -------------------------------------------------------------------------------------------------
# ---------------- Build object completion items --------------------------------------------------
# -------------------------------------------------------------------------------------------------

   method buildObjectCompletionItems(results_table, objectName)
      local className, class_method, class_attribute
      className := objects[objectName]

      if member(linked_classes, className) then {
         every class_method := key(linked_classes[className]["methods"]) do {
            put(results_table["items"], table("label", class_method, "kind", 2))
         }
         every class_attribute := linked_classes[className]["attributes"][1 to *linked_classes[className]["attributes"]] do {
            put(results_table["items"], table("label", class_attribute["name"], "kind", 5))
         }
      }
      if member(imported_classes, className) then {
         every class_method := key(imported_classes[className]["methods"]) do {
            put(results_table["items"], table("label", class_method, "kind", 2))
         }
         every class_attribute := imported_classes[className]["attributes"][1 to *imported_classes[className]["attributes"]] do {
            put(results_table["items"], table("label", class_attribute["name"], "kind", 5))
         }
      }
      if member(internal_classes, className) then {
         every class_method := key(internal_classes[className]["methods"]) do {
            put(results_table["items"], table("label", class_method, "kind", 2))
         }
         every class_attribute := internal_classes[className]["attributes"][1 to *internal_classes[className]["attributes"]] do {
            put(results_table["items"], table("label", class_attribute["name"], "kind", 5))
         }
      }
      if member(package_classes, className) then {
         every class_method := key(package_classes[className]["methods"]) do {
            put(results_table["items"], table("label", class_method, "kind", 2))
         }
         every class_attribute := package_classes[className]["attributes"][1 to *package_classes[className]["attributes"]] do {
            put(results_table["items"], table("label", class_attribute["name"], "kind", 5))
         }
      }
   end

   method buildPackageConstructorItems(results_table, packageName)
      local _file, _class, _constructor
      if member(lsp_database.package_db, packageName) then {
         every _file := key(lsp_database.package_db[packageName]["files"]) do {
            every _class := key(lsp_database.package_db[packageName]["files"][_file]["classes"]) do {
               _constructor := lsp_database.package_db[packageName]["files"][_file]["classes"][_class]["constructor"]
               put(results_table["items"], table("label", _constructor["name"], "kind", 4))
            }
         }
      }
   end

   method getPackages()
      return lsp_database.package_db
   end

   method getFileNames()
      local pack, _file, _files
      _files := []
      every pack := key(lsp_database.package_db) do {
         every _file := key(lsp_database.package_db[pack]["files"]) do
            put(_files, _file)
      }
      return _files
   end

   method getContext(lineNum, charNum)
      if /context | context.isNewContext(lineNum, charNum) then {
         # The constructor could fail if the file doesn't exist
         context := Context(temp_idoc, uri, lineNum, charNum) | fail
      }
      return context
   end

# -------------------------------------------------------------------------------------------------
# ---------------- initially ----------------------------------------------------------------------
# -------------------------------------------------------------------------------------------------

   initially
      setFile()
      identifiers := &letters ++ &digits ++ '_' ++ '-'
end

class Context(uniAll, uri, line, lineNum, charNum, objectName, methodName, contextType, packageName, totalFileLines)
   method getDesiredLine(lineNum)
      line := uniAll.getUniFileLine(uri, lineNum)
   end

   # Retrieve object name #
   method getObjectName()
      local temp, c, name
      line ? {
         temp := tab(charNum)
         while c := move(-1) do {
            if (c ** identifiers) ~== c then {
               break
            }
         }

         if ="." then {
            move(-1)
            while c := move(-1) do {
               if (c ** identifiers) ~== c then {
                  move(1)
                  break
               }
            }
            name := tab(upto("."))
            return name
         }
      }
   end

   # Retrive method name #
   method getMethodName()
      local temp, c, _method
      line ? {
         temp := tab(charNum)
         while c := move(-1) do {
            if (c ** identifiers) ~== c then {
               break
            }
         }

         if &pos ~= 1 then move(1)

         _method := tab(upto("("))
         return _method
      }
   end

   method detectContextType()
      local objectName, single_quote, double_quote, ch, backslash_count, temppos, c

      #-----------------------------------------------#
      #----------- Case "start of file" --------------#
      #-----------------------------------------------#

      \line | fail

      #-----------------------------------------------#
      #-------- Case "after package or import" -------#
      #-----------------------------------------------#

      #if ReMatch("^[\ \t]*global[\ \t]+[a-zA-Z0-9_]+", _line) then

      if ReMatch("^[\ \t]*package[\ \t]+", line) | ReMatch("^[\ \t]*import[\ \t]+", line) then {
         return "package"
      }

      #-----------------------------------------------#
      #-------- Case "after link" --------------------#
      #-----------------------------------------------#

      if ReMatch("^[\ \t]*link[\ \t]+", line) then {
         return "link"
      }

      #-----------------------------------------------#
      #------- Case "inside comment or string" -------#
      #-----------------------------------------------#

      line ? {
         single_quote := 0
         double_quote := 0
         backslash_count := 0
         while (&pos < charNum) do {
            ch := move(1) | break
            if ch == "\'" then {
               temppos := &pos
               move(-1)
               while move(-1) == "\\" do backslash_count +:= 1
               if (backslash_count % 2) = 0 then single_quote +:= 1
               backslash_count := 0
               &pos := temppos
            }
            if ch == "\"" then {
               temppos := &pos
               move(-1)
               while move(-1) == "\\" do backslash_count +:= 1
               if (backslash_count % 2) = 0 then double_quote +:= 1
               backslash_count := 0
               &pos := temppos
            }
            if (ch == "#") & ((single_quote % 2) = 0) & ((double_quote % 2) = 0) then return "comment"
         }
         if ((single_quote % 2) ~= 0 | (double_quote % 2) ~= 0) then return "string"
      }

      #-----------------------------------------------#
      #--------- Case "package dual colon" -----------#
      #-----------------------------------------------#

      line ? {
         tab(charNum)
         move(-2)
         if move() == ":" & move() == ":" then {
            move(-2)
            temppos := &pos
            while c := move(-1) do {
               if (c ** identifiers) ~== c then {
                  break
               }
            }
            if &pos ~= 1 then move(1)
            packageName := tab(temppos)
            return "packdualcolon"
         }
      }

      #-----------------------------------------------#
      #-------- Case "object" ------------#
      #-----------------------------------------------#

      if objectName := getObjectName() then {
         self.objectName := objectName
         self.methodName := getMethodName()
         return "object"
      }

      #-----------------------------------------------#
      #-------- Case "method" ------------#
      #-----------------------------------------------#

      if methodName := getMethodName() then
         self.methodName := getMethodName()
         return "method"

      #-----------------------------------------------#
      #---------------- Default case -----------------#
      #-----------------------------------------------#
   end

   method isNewContext(lineNumber, charNumber)
      if lineNum ~= lineNumber | charNum ~= charNumber then
        return
   end

   method getCase()
      return contextType
   end

   initially(uniAll, uri, lineNum, charNum)
      local f
      self.uniAll := uniAll
      self.uri := uri
      self.lineNum := lineNum
      self.charNum := charNum
      line := getDesiredLine(lineNum)
      contextType := detectContextType()
      totalFileLines := 0
      f := open(uri) | fail
      if stat(uri).size > 0 then {
         reads(f, stat(uri).size) ?
            every upto('\n') do
               totalFileLines +:= 1
      }
      f := close(f)
end

# Creates a working comments
procedure makeComment(_function)
   local paramComment, returnComment, failsComment, genComment, normalComment, firstTen, currentType, insideComment,
   tabCharacter, _comment, paramCounter, returnCounter, failsCounter, genCounter, _line, paramPos, returnsPos,
   failsPos, genPos, firstKey, fullComment, paramList, _param, paramName, sourceComment, srcFile
   paramComment:= ""
   returnComment := ""
   failsComment := ""
   genComment := ""
   sourceComment := ""
   normalComment := ""
   firstTen := ""
   currentType := 0
   insideComment := 0
   tabCharacter := "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
   paramList := []

   every _param := _function.getParams().get() do {
      paramName := _param.getName()
      put(paramList, paramName)
   }

   every _comment := _function.getComments().get() do {
      paramCounter := 1
      returnCounter := 0
      failsCounter := 0
      genCounter := 0
      every _line := _comment.get() do {
         _line := replace(_line, "\\n", "\\\\n")

         if((find("<[", _line))) then
            insideComment := 1
         if((find("]>", _line))) then
            insideComment := 0

         if((find("<[", _line)) | (find("]>", _line))) then {

            if(find("param", _line)) then
               paramPos := find("param", _line)
            else
               paramPos := 9999

            if (find("returns", _line)) then
               returnsPos := find("returns", _line)
            else
               returnsPos := 9999

            if (find("fails", _line)) then
               failsPos := find("fails", _line)
            else
               failsPos := 9999

            if (find("generates", _line)) then
               genPos := find("generates", _line)
            else
               genPos := 9999

            firstKey := min(paramPos, returnsPos, failsPos, genPos)

            if ((find("param", _line[firstKey:firstKey+5]) & (find("<[", _line)))) then {
               if \paramList then
                  _line := _line[1:firstKey+6] || "*" || _line[firstKey+6:firstKey+6+*paramList[paramCounter]] || "*:" || _line[firstKey+6+*paramList[paramCounter]:*_line+1]

               if(paramCounter == 1) then
                  _line := replace(_line[1:firstKey+5], "param", "\n **Parameters:**  \n \\\\>" || tabCharacter) || _line[firstKey+6:0]
               else
                  _line := replace(_line[1:firstKey+5], "param", "\n \\\\>" || tabCharacter) || _line[firstKey+6:0]


               paramCounter +:= 1
               paramComment ||:= _line
               currentType := 1
            }
            else if ((find("returns", _line[firstKey:firstKey+7]) & (find("<[", _line)))) then {
               if(returnCounter == 0) then {
                  _line := replace(_line[1:firstKey+7], "returns", "\n **Returns:**  \n " || tabCharacter) || _line[firstKey+8:0]
                  returnCounter +:= 1
               }
               else
                  _line := replace(_line[1:firstKey+7], "returns", "\n " || tabCharacter) || _line[firstKey+8:0]

               returnComment ||:= _line
               currentType := 2
            }
            else if ((find("fails", _line[firstKey:firstKey+5]) & (find("<[", _line)))) then {
               if(failsCounter == 0) then {
                  _line := replace(_line[1:firstKey+5], "fails", "\n **Fails:**  \n " || tabCharacter) || _line[firstKey+6:0]
                  failsCounter +:= 1
               }
               else
                  _line := replace(_line[1:firstKey+5], "fails", "\n " || tabCharacter) || _line[firstKey+6:0]

               failsComment ||:= _line
               currentType := 3
            }
            else if ((find("generates", _line[firstKey:firstKey+9]) & (find("<[", _line)))) then {
               if(genCounter == 0) then {
                  _line := replace(_line[1:firstKey+9], "generates", "\n **Generates:**  \n " || tabCharacter) || _line[firstKey+10:0]
                  genCounter +:= 1
               }
               else
                  _line := replace(_line[1:firstKey+9], "generates", "\n " || tabCharacter) || _line[firstKey+10:0]

               genComment ||:= _line
               currentType := 4
            }
            else
               if currentType = 1 then paramComment ||:= _line
               else if currentType = 2 then returnComment ||:= _line
               else if currentType = 3 then failsComment ||:= _line
               else if currentType = 4 then genComment ||:= _line
         }
         else if (insideComment = 1) then {
            if currentType = 1 then paramComment ||:= _line
            else if currentType = 2 then returnComment ||:= _line
            else if currentType = 3 then failsComment ||:= _line
            else if currentType = 4 then genComment ||:= _line
         }
         else if ReMatch("^[\ ]*Source: ", _line) then {
            sourceComment := replace(_line, "Source:", "\n\n **Source:**")
         }
         else
            normalComment ||:= _line
      }
   }

   if srcFile := _function.getSrcFile() & sourceComment == "" then
      sourceComment := "\n\n **Source:** " || srcFile

   fullComment := normalComment || "\n \n --- \n \n" || " " || returnComment || " " || paramComment || " " || failsComment || " " || genComment || " " || sourceComment
   fullComment := replacem(fullComment, "<p>", "", "</p>", "")
   fullComment := replacem(fullComment, "<i>", "_", "</i>", "_")
   fullComment := replacem(fullComment, "<b>", "**", "</b>", "**")
   fullComment := replacem(fullComment, "<tt>", "`", "</tt>", "`")
   fullComment := replace(fullComment, "\"", "\\\"")
   fullComment := replace(fullComment, "<[", "")
   fullComment := replace(fullComment, "]>", "")

   return fullComment
end
