' Gambas module file

Public Password As New Collection

Public Const METADATA_TABLE_PREFIX As String = "__gb_metadata"
Public Const OLD_METADATA_TABLE_NAME As String = "__gb_metadata"
Public Const METADATA_TABLE_NAME As String = "__gb_metadata_v2"

Public Const VOID_PASSWORD As String = "\x00"

Public Enum MD_VERSION = 0, MD_FIELD = 1, MD_INDEX = 2, MD_TABLE = 3, MD_FOREIGN_KEY = 4

Private $bError As Boolean

Public Sub MakeAbsolutePath(sPath As String) As String
  
  If sPath = "~" Then
    sPath = User.Home
  Else If sPath Begins "~/" Then
    sPath = User.Home &/ Mid$(sPath, 3)
  Else If File.IsRelative(sPath) Or If Not sPath Then 
    sPath = Project.Dir &/ sPath
  Endif
  
  Return sPath
  
End

Public Sub InitFrom(hConn As Connection, hConfig As Object, sName As String, Optional bWithDatabase As Boolean, Optional sPassword As String) 

  Dim sType As String
  
  sType = LCase(hConfig["Connection/Type"])
  
  If sType = "template" Then
    
    hConn.Type = "sqlite"
    hConn.Host = Project.Dir &/ ".connection"
    If bWithDatabase Then hConn.Name = sName & ".db"
    
    Return
    
  Endif
    
  hConn.Type = sType
  If sType = "sqlite" Then
    hConn.Host = MakeAbsolutePath(hConfig["Connection/Path"])
  Else
    hConn.Host = hConfig["Connection/Host"]
    hConn.Port = hConfig["Connection/Port"]
    hConn.User = hConfig["Connection/User"]
    If Not hConfig["Connection/NoPassword"] Then
      If sPassword Then 
        hConn.Password = sPassword
      Else
        hConn.Password = LoadPassword(sName, hConfig["Connection/RememberPassword"])
      Endif
    Endif
  Endif
  
  If bWithDatabase Then hConn.Name = hConfig["Connection/Database"]
  
  hConn.IgnoreCharset = hConfig["/Connection/IgnoreCharset"]
  
End

Private Sub ErrorMessage(sMsg As String, sError As String)

  If sError Then
    If Not $bError Then
      $bError = True
      FMain.ShowErrorWith(sMsg, sError)
    Endif
  Else
    $bError = False
  Endif

End

Public Sub LoadPassword(sName As String, bDesktop As Boolean) As String
  
  Dim sPassword As String
  
  sPassword = Password[sName]
  If Not sPassword Then 
    If bDesktop Then
      Error.Clear
      Try sPassword = Desktop.Passwords[Project.Name &/ "Connection" &/ sName]
      ErrorMessage(("Unable to retrieve password."), Error.Text)
      Password[sName] = sPassword
    Endif
  Endif
  
  Return sPassword
  
End

Public Sub SavePassword(sName As String, sPassword As String, (bDesktop) As Boolean)
  
  Password[sName] = sPassword
  If sPassword Then 
    Error.Clear
    Try Desktop.Passwords[Project.Name &/ "Connection" &/ sName] = sPassword
    ErrorMessage(("Unable to save password."), Error.Text)
  Endif
  
End


Public Function WidthFromType(hCtrl As Control, iType As Integer, iLength As Integer, sTitle As String) As Integer

  Dim iWidth As Integer

  Select Case iType

    Case gb.Boolean
      iWidth = hCtrl.Font.TextWidth(Str(False)) + 32

    Case gb.Integer
      iWidth = hCtrl.Font.TextWidth("1234567890") + 8

    Case gb.Long
      iWidth = hCtrl.Font.TextWidth("1234567890") * 2 + 8

    Case gb.Float
      iWidth = hCtrl.Font.TextWidth(CStr(Pi) & "E+999") + 8

    Case gb.Date
      iWidth = hCtrl.Font.TextWidth(Str(Now)) + 8

    Case gb.String
      If iLength = 0 Then iLength = 255
      iLength = Min(32, iLength)
      iWidth = hCtrl.Font.TextWidth("X") * iLength + 8

  End Select

  iWidth = Max(iWidth, hCtrl.Font.TextWidth(sTitle) + 8)

  Return iWidth

End

Public Function TypeToString(iType As Integer) As String

  Select Case iType
    Case db.Integer
      Return ("Integer")
    Case db.Long
      Return ("Long")
    Case db.Float
      Return ("Float")
    Case db.Date
      Return ("Date")
    Case db.Boolean
      Return ("Boolean")
    Case db.String
      Return ("String")
    Case db.Serial
      Return ("Serial")
    Case db.Blob
      Return ("Blob")
    Default
      Return "?"
  End Select

End

Public Function TypeFromString(sType As String) As Integer

  Select Case sType
    Case ("Integer")
      Return db.Integer
    Case ("Long")
      Return db.Long
    Case ("Float")
      Return db.Float
    Case ("Date")
      Return db.Date
    Case ("Boolean")
      Return db.Boolean
    Case ("String")
      Return db.String
    Case ("Serial")
      Return db.Serial
    Case ("Blob")
      Return db.Blob
    Default
      Return db.Integer
  End Select

End

Public Function GetTempTableName(Handle As Connection) As String

  Dim iInd As Integer
  Dim sTemp As String

  For iInd = 0 To 99
    sTemp = "_gambas_table"
    If iInd Then sTemp = sTemp & "_" & iInd
    If Not Handle.Tables.Exist(sTemp) Then Break
    Inc iInd
    sTemp = ""
  Next

  If Not sTemp Then Error.Raise("Cannot find a free temporary table name")

  Return sTemp

End

Public Function CopyTableData(Handle As Connection, sTable As String) As String

  Dim sTemp As String
  Dim iInd As Integer
  Dim sReq As String
  Dim hSrc As Table
  Dim hDst As Table
  Dim rSrc As Result
  Dim rDst As Result
  Dim hField As Field
  Dim sError As String

  sTemp = GetTempTableName(Handle)

  Select Case Handle.Type

    Case "postgresql"
      sReq = "SELECT * INTO TABLE " & sTemp & " FROM \"" & sTable & "\""
      Handle.Exec(sReq)

    Case "mysql"
      sReq = "CREATE TABLE " & sTemp & " SELECT * FROM `" & sTable & "`"
      Handle.Exec(sReq)

    'CASE "sqlite"
    '  sReq = "INSERT INTO " & sTemp & " SELECT * FROM " & sTable

    Default

      hSrc = Handle.Tables[sTable]
      hDst = Handle.Tables.Add(sTemp, hSrc.Type)

      'IF NOT hSrc.PrimaryKey THEN Error.Raise("No primary key")

      For Each hField In hSrc.Fields
        With hField
          hDst.Fields.Add(.Name, .Type, .Length, .Default)
        End With
      Next

      hDst.PrimaryKey = hSrc.PrimaryKey

      hDst.Update

      Handle.Begin

      rSrc = Handle.Find(sTable)
      rDst = Handle.Create(sTemp)

      For Each rSrc
'         FOR EACH hField IN rSrc.Fields
'           sField = hField.Name
'           rDst[sField] = rSrc[sField]
'         NEXT
        For iInd = 0 To rSrc.Fields.Count - 1
          rDst[iInd] = rSrc[iInd]
        Next
        rDst.Update
      Next

      Handle.Commit

  End Select

  Return sTemp

Catch

  sError = Error.Text

  Select Case Handle.Type

    Case "postgresql"

    Case "mysql"
      Try Handle.Rollback
      If Handle.Tables.Exist(sTable) Then
         Try Handle.Tables.Remove(sTemp)
      Else
         If Handle.Tables.Exist(sTemp) Then
            sReq = "RENAME TABLE " & sTemp & " TO " & sTable
            Try Handle.Exec(sReq)
         Else
            Error.Raise("Severe Error: Table has been lost!!")
         Endif
      Endif

    Default
      Try Handle.Rollback
      If Handle.Tables.Exist(sTable) Then
         Try Handle.Tables.Remove(sTemp)
      Else
         If Handle.Tables.Exist(sTable) Then
            Error.Raise("Severe Error: Table " & sTable & " has not been recreated. Data held in " & sTemp)
         Endif
      Endif

  End Select

  Error.Raise("Cannot copy table data: " & sError)

End

Public Function CountTableData(Handle As Connection, sTable As String) As Integer

  Dim rResult As Result

  rResult = Handle.Exec("SELECT COUNT(*) AS nrecord FROM " & Handle.Quote(sTable, True))
  Return rResult["nrecord"]

End

Public Function CreateTable(Handle As Connection, sTable As String, sType As String) As Boolean

  Dim hTable As Table

  If Handle.Tables.Exist(sTable) Then
    Message.Warning(Subst(("Table '&1' already exists."), sTable))
    Return True
  Endif

  hTable = Handle.Tables.Add(sTable, sType)
  With hTable
    .Fields.Add("id", gb.Integer)
    .PrimaryKey = ["id"]
    .Update
  End With

Catch

  Message(Subst(("Cannot create table '&1'."), sTable) & "\n\n" & Error.Text)
  Return True

End


Public Sub GetConnections() As String[]
  
  Dim sFile As String
  Dim aConn As New String[]
  
  For Each sFile In Dir(Project.Dir &/ ".connection", "*.connection")
    aConn.Add(File.BaseName(sFile))
  Next
  aConn.Sort

Finally 

  Return aConn
  
End

Public Sub GetTemplates() As String[]
  
  Dim sFile As String
  Dim aConn As New String[]
  
  For Each sFile In Dir(Project.Dir &/ ".connection", "*.template")
    sFile = File.BaseName(sFile)
    If Not Exist(Project.Dir &/ ".connection" &/ sFile & ".connection") Then Continue
    aConn.Add(sFile)
  Next
  aConn.Sort

Finally 

  Return aConn
  
End

Public Sub FillViewWithTables(lvwTable As TreeView, hConn As Connection, bShowSystem As Boolean)
  
  Dim hTable As Table
  Dim sName As String
  Dim sParent As String
  Dim iPos As Integer
  
  lvwTable.Clear
  
  'lvwTable.Add("$", ("SQL request"), Picture["img/16/sql.png"])
  For Each hTable In hConn.Tables
    
    sName = hTable.Name
    
    If Not bShowSystem Then
      If hTable.System Or If sName Begins METADATA_TABLE_PREFIX Then Continue
    Endif
    
    iPos = InStr(sName, ".")
    If iPos Then
      sParent = ">" & Left(sName, iPos - 1)
      sName = Mid$(sName, iPos + 1)
    Else
      sParent = ""
    Endif
    
    If sParent And If Not lvwTable.Exist(sParent) Then
      lvwTable.Add(sParent, Mid$(sParent, 2), Picture["icon:/small/directory"])
    Endif
    
    If hTable.System Then
      lvwTable.Add("S" & hTable.Name, sName, Picture["icon:/small/program"], sParent)
    Else
      lvwTable.Add("T" & hTable.Name, sName, Picture["icon:/small/table"], sParent)
    Endif
  Next
  
  lvwTable.Sorted = True
  lvwTable.Sorted = False
  
End

Public Sub NeedPassword(sType As String) As Boolean
  
  sType = LCase(sType)
  Return sType <> "sqlite" And sType <> "template"
  
End

Public Sub CheckPassword(sPath As String) As Boolean
  
  Dim hConfig As Settings
  Dim hConn As New Connection
  Dim iBusy As Integer 
  Dim bRet As Boolean
  
  iBusy = Application.Busy
  Application.Busy = 0
  
  hConfig = New Settings(sPath)
  InitFrom(hConn, hConfig, File.BaseName(sPath))
  If NeedPassword(hConn.Type) And If Not hConfig["Connection/NoPassword"] And If Not hConn.Password Then 
    If FNewConnection.Closed Then
      bRet = FNewConnection.Run(sPath, True)
    Else
      bRet = True
    Endif
  Endif
  
  Application.Busy = iBusy
  Return bRet
  
End

Public Sub GetFields(hConn As Connection, sTable As String, Optional bSort As Boolean) As String[]

  Dim hTable As Table
  Dim hField As Field
  Dim aField As New String[]
  
  hTable = hConn.Tables[sTable]
  For Each hField In hTable.Fields
    aField.Add(hField.Name)
  Next
  If bSort Then aField.Sort(gb.Natural)
  Return aField
  
Catch
  
End

Public Sub Convert(vVal As Variant, iType As Integer) As Variant

  Select iType
    Case db.Float
      vVal = Val(vVal)
      If TypeOf(vVal) <= gb.Float Then Return vVal
    Case db.Date
      vVal = Val(vVal)
      If TypeOf(vVal) = gb.Date Then Return vVal
  End Select
  
  Error.Raise("Type mismatch")

End

Public Sub CreateMetadataTable(hConn As Connection) As Boolean
  
  Dim hTable As Table
  Dim rTable As Result
  Dim rOldTable As Result
  Dim sErr As String

  If hConn.Tables.Exist(METADATA_TABLE_NAME) Then Return

  hTable = hConn.Tables.Add(METADATA_TABLE_NAME)
  With hTable
    .Fields.Add("sTableName", gb.String)
    .Fields.Add("iType", gb.Integer)
    .Fields.Add("sKey", gb.String)
    .Fields.Add("sValue", gb.String)
    .PrimaryKey = ["sTableName", "iType", "sKey"]
    .Update
  End With
  
  If hConn.Tables.Exist(OLD_METADATA_TABLE_NAME) Then
    hConn.Begin
    rOldTable = hConn.Find(OLD_METADATA_TABLE_NAME)
    While rOldTable.Available
      rTable = hConn.Create(METADATA_TABLE_NAME)
      rTable!sTableName = rOldTable!sTable
      rTable!iType = rOldTable!iType
      rTable!sKey = rOldTable!sKey
      rTable!sValue = rOldTable!sValue
      rTable.Update
      rOldTable.MoveNext
    Wend
    hConn.Tables.Remove(OLD_METADATA_TABLE_NAME)
    hConn.Commit
  Endif

Catch

  sErr = Error.Text
  hConn.Rollback
  Message(("Cannot create metadata table.") & "\n\n" & sErr)
  Return True

End

Public Sub CreateSQLite(sPath As String) As String
  
  Dim sDir As String
  Dim iInd As Integer
  Dim sName As String
  Dim hConfig As Settings
  
  sDir = Project.Dir &/ ".connection"
  If Not Exist(sDir) Then Project.InsertDirectory(sDir)
  
  sName = File.BaseName(sPath)
  If Exist(sDir &/ sName) Then
    For iInd = 2 To 100
      If Not Exist(sDir &/ sName & CStr(iInd)) Then Break
    Next
    sName &= CStr(iInd)
  Endif

  hConfig = New Settings(sDir &/ sName, Trim(Mid$(Project.CONNECTION_MAGIC, 2)))
  hConfig["Connection/Type"] = "sqlite"
  hConfig["Connection/Path"] = File.Dir(sPath)
  hConfig["Connection/RememberPassword"] = False
  hConfig["Connection/IgnoreCharset"] = False
  hConfig["Connection/DisplayMetadata"] = False
  hConfig["Connection/RememberTemplate"] = False
  hConfig["Connection/Database"] = File.Name(sPath)
  hConfig.Save
  
  Project.InsertFile(sName, sDir)
  Return sDir &/ sName
  
End
