;
; ------------------------------------------------------------
;
;   PureBasic - polyglot loader
;
;    (c) 2015 - trisymphony.com
;
; ------------------------------------------------------------

; -- LOCALS --------------------------------------------------

Structure pg_langstruct
  langstring.s
  version.i
  location.s
EndStructure

Structure pg_dict
  Map dict.s()
EndStructure

Global NewMap availableLanguages.pg_langstruct()
Global NewMap languageDict.pg_dict()

Global pg_bInit = 0
#compound = 1

Global *MemoryID


; -- WINDOWS DLL ---------------------------------------------

CompilerIf #PB_Compiler_OS = #PB_OS_Windows
  
  ; This procedure is called once, when the program loads the library
  ProcedureDLL AttachProcess(Instance)
  EndProcedure
  
  ; Called when the program releases (free) the DLL
  ProcedureDLL DetachProcess(Instance)
  EndProcedure
  
  ; Both are called when a thread in a program calls or releases (frees) the DLL
  ProcedureDLL AttachThread(Instance)
  EndProcedure
  
  ProcedureDLL DetachThread(Instance)
  EndProcedure
  
CompilerEndIf


; -- LIBRARY CODE --------------------------------------------

; init and load all available XMLs
ProcedureDLL pg_init(XMLFOLDER.s, SCHEME.s="Polyglot-*.xml", VERSION.i=0 ) ; version 0 means take most current version, otherwise take the specified version
  
  ; return codes for communication
  ; 0 - not initialized
  ; 1 - loaded and ready
  ; 2 - XML folder does not exist
  ; 3 - XML parsing error
  
  Static pXMLFOLDER.s
  Static pSCHEME.s
  pXMLFOLDER = PeekS(@XMLFOLDER, -1, #PB_UTF8)
  pSCHEME = PeekS(@SCHEME, -1, #PB_UTF8)
  ; cleanup input: remove end slashes
  mXMLFOLDER.s = RTrim(pXMLFOLDER, Chr(47))
  mXMLFOLDER.s = RTrim(pXMLFOLDER, Chr(92))
  
  ; check for provided XML folder
  If FileSize(mXMLFOLDER) = -2
    
    ; scan directory for polyglot language xml files
    If ExamineDirectory(0, mXMLFOLDER, pSCHEME)
      While NextDirectoryEntry(0)
        If DirectoryEntryType(0) = #PB_DirectoryEntry_File
          
          ; disassemble file name
          FILENAME.s = DirectoryEntryName(0)
          ; get version info
          FILEVERSION = Val(Left(StringField(FILENAME, 2, "-"), 3))
          
          
          ; lang codes
          For k=2 To CountString(FILENAME, "_")+1
            NEWLANG.s = ReplaceString(StringField(FILENAME, k, "_"), ".xml", "")
            
            ; version check
            If FindMapElement(availableLanguages(), NEWLANG) ; entry already exists -> get version number
              CURRENTVERSION = availableLanguages(NEWLANG)\version
            Else ; not found -> uninitialized
              availableLanguages(NEWLANG)\version = 0
            EndIf
            
            If (VERSION = 0); compare versions and take the most recent one
              If (CURRENTVERSION < FILEVERSION)
                availableLanguages(NEWLANG)\langstring = NEWLANG
                availableLanguages(NEWLANG)\version = FILEVERSION ; for later comparison
                ; store file location
                CompilerIf #PB_Compiler_OS = #PB_OS_Windows
                  availableLanguages()\location = mXMLFOLDER + "\" + FILENAME
                CompilerElse
                  availableLanguages()\location = mXMLFOLDER + "/" + FILENAME
                CompilerEndIf
              EndIf
              
            ElseIf (VERSION = FILEVERSION) ; only if we specified a certain version number
              availableLanguages(NEWLANG)\langstring = NEWLANG
              availableLanguages(NEWLANG)\version = 0 ; we don't care
              ; store file location
              CompilerIf #PB_Compiler_OS = #PB_OS_Windows
                availableLanguages()\location = mXMLFOLDER + "\" + FILENAME
              CompilerElse
                availableLanguages()\location = mXMLFOLDER + "/" + FILENAME
              CompilerEndIf
            EndIf

          Next
          
        EndIf
      Wend
      FinishDirectory(0)
    EndIf ; end directory scan
    
    
    ; hack apart XML and store into global maps
    ForEach availableLanguages()
      If LoadXML(0, availableLanguages()\location, #PB_UTF8)
        If XMLStatus(0) <> #PB_XML_Success
          ProcedureReturn 3
        EndIf
        
        *MainNode = MainXMLNode(0)
        If *MainNode
          ; Now get the first child node
          *ChildNode = ChildXMLNode(*MainNode)
          ; Loop through all available child nodes 
          While *ChildNode <> 0
            If GetXMLNodeName(*ChildNode) = "s"
              PGCODE.s = GetXMLAttribute(*ChildNode, "n")
              PGVAL.s = GetXMLNodeText(*ChildNode)
              languageDict(availableLanguages()\langstring)\dict(PGCODE) = PGVAL
            EndIf
            *ChildNode = NextXMLNode(*ChildNode)
          Wend ; end childnode
          
        EndIf ; end mainnode
      FreeXML(0)
      EndIf ; end XML load
      
    Next ; end availLang loop
    
    CreateRegularExpression(#compound, "[A-Z]+(_[A-Z]+)+")
    pg_bInit = 1
    ProcedureReturn 1
    
  Else
    ProcedureReturn 2
  EndIf ; end folder check
  
  ProcedureReturn 0
EndProcedure


; check if one particular language is loaded
ProcedureDLL pg_isLanguageAvailable(LANGCODE.s)
  
  Static pLANGCODE.s
  pLANGCODE = PeekS(@LANGCODE, -1, #PB_UTF8)
  
  If pg_bInit = 1
    If FindMapElement(languageDict(), pLANGCODE)
      ProcedureReturn 1
    Else
      ProcedureReturn 0
    EndIf
  EndIf
  
  ProcedureReturn 0
EndProcedure


; translate string from code string
ProcedureDLL pg_translate(LANGCODE.s, CODESTRING.s, bLC.i)
  Static pg_returnTranslatedString.s
  Static pLANGCODE.s
  Static pCODESTRING.s
  pLANGCODE = PeekS(@LANGCODE, -1, #PB_UTF8)
  pCODESTRING = PeekS(@CODESTRING, -1, #PB_UTF8)
  
  If pg_bInit = 1
    If FindMapElement(languageDict() ,pLANGCODE)
      If bLC = 0
        pg_returnTranslatedString = languageDict(pLANGCODE)\dict(pCODESTRING)
      Else
        pg_returnTranslatedString = LCase(languageDict(pLANGCODE)\dict(pCODESTRING))
      EndIf
    Else
      pg_returnTranslatedString = "NOCODE"
    EndIf
  Else
    pg_returnTranslatedString = "NOINIT"
  EndIf
  
  Static *String
  If *String
    FreeMemory(*String)
  EndIf
  
  *String = AllocateMemory(StringByteLength(pg_returnTranslatedString, #PB_UTF8)+1)
  PokeS(*String, pg_returnTranslatedString, -1, #PB_UTF8)
  ProcedureReturn *String
EndProcedure


; translate string that contains code strings among regular strings
ProcedureDLL pg_translateCompound(LANGCODE.s, CODESTRING.s, bLC.i)
  Static pg_returnTranslatedString.s
  Static pLANGCODE.s
  Static pCODESTRING.s
  pLANGCODE = PeekS(@LANGCODE, -1, #PB_UTF8)
  pCODESTRING = PeekS(@CODESTRING, -1, #PB_UTF8)
  pg_returnTranslatedString = pCODESTRING
  lcstr.s = ""
  
  If pg_bInit = 1
    ; regex
    If IsRegularExpression(#compound) And FindMapElement(languageDict() ,pLANGCODE)
      Dim Result$(0)
      NbFound = ExtractRegularExpression(#compound, pCODESTRING, Result$())
      
      For k = 0 To NbFound-1
        If FindMapElement(languageDict(pLANGCODE)\dict(), Result$(k))
          If bLC = 0
            lcstr = languageDict(pLANGCODE)\dict(Result$(k))
          Else
            lcstr = LCase(languageDict(pLANGCODE)\dict(Result$(k)))
          EndIf
          pg_returnTranslatedString = ReplaceString(pg_returnTranslatedString, Result$(k), lcstr)
        EndIf
      Next
      
    EndIf
  EndIf
  
  Static *String
  If *String
    FreeMemory(*String)
  EndIf
  
  *String = AllocateMemory(StringByteLength(pg_returnTranslatedString, #PB_UTF8)+1)
  PokeS(*String, pg_returnTranslatedString, -1, #PB_UTF8)
  ProcedureReturn *String
EndProcedure

; IDE Options = PureBasic 5.24 LTS (Windows - x64)
; ExecutableFormat = Shared Dll
; CursorPosition = 126
; FirstLine = 182
; Folding = -8
; EnableUnicode
; EnableThread
; Executable = polyglot64-utf8.dll