###############################################################################
#
# session.eagle --
#
# Written by Joe Mistachkin.
# Released to the public domain, use at your own risk!
#
###############################################################################

package require Eagle
package require Eagle.Library
package require Eagle.Test

runTestPrologue

###############################################################################

package require System.Data.SQLite.Test
runSQLiteTestPrologue

###############################################################################

proc getSomeText { random count } {
  set items [list \
      Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel \
      India Juliet Kilo Lima Mike November Oscar Papa \
      Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray \
      Yankee Zulu]

  if {!$random} then {
    variable some_text_index

    if {![info exists some_text_index]} then {
      set some_text_index 0
    }
  }

  set length [llength $items]
  set result [list]

  for {set i 0} {$i < $count} {incr i} {
    if {$random} then {
      set item [lindex $items [expr {int(rand() * $length)}]]
    } else {
      set item [lindex $items $some_text_index]
      incr some_text_index

      if {$some_text_index >= $length} then {
        set some_text_index 0
      }
    }

    lappend result $item
  }

  return $result
}

###############################################################################

proc cleanupSomeText {} {
  variable some_text_index
  unset -nocomplain some_text_index
}

###############################################################################

proc forDisplay { object member } {
  set isArray false

  if {[isObjectHandle $object] && $object ne "null"} then {
    set value [object invoke -create -- $object $member]

    if {[isObjectHandle $value] && $value ne "null"} then {
      set isArray [object invoke -- $value GetType.IsArray]
    }
  } else {
    set value null
  }

  if {$isArray} then {
    return [object invoke -flags +NonPublic \
        -parametertypes Array System.Data.SQLite.HelperMethods \
        ToDisplayString $value]
  } else {
    return [object invoke -flags +NonPublic \
        System.Data.SQLite.HelperMethods ToDisplayString $value]
  }
}

###############################################################################

proc changeSetToString { changeSet includeValues } {
  set result [list]

  object foreach -alias item $changeSet {
    lappend result TableName [$item TableName]
    lappend result NumberOfColumns [$item NumberOfColumns]
    lappend result OperationCode [$item OperationCode]
    lappend result Indirect [$item Indirect]

    lappend result PrimaryKeyColumns \
        [forDisplay $item PrimaryKeyColumns]

    if {$includeValues} then {
      set numberOfColumns [$item NumberOfColumns]

      for {set index 0} {$index < $numberOfColumns} {incr index} {
        set oldValue [$item GetOldValue $index]

        lappend result OldValue $index \
            [forDisplay $oldValue GetObject]

        set newValue [$item GetNewValue $index]

        lappend result NewValue $index \
            [forDisplay $newValue GetObject]

        set conflictValue [$item GetConflictValue $index]

        lappend result ConflictValue $index \
            [forDisplay $conflictValue GetObject]
      }
    }
  }

  return $result
}

###############################################################################

proc createTheSchema { db } {
  sql execute $db {
    CREATE TABLE t1(x INTEGER PRIMARY KEY, y TEXT);
  }
}

###############################################################################

proc makeSomeChanges { db types random {count 5} } {
  foreach type $types {
    switch -nocase -- $type {
      insert {
        set text [appendArgs "inserted: " [getSomeText $random $count]]

        sql execute $db {
          INSERT INTO t1(y) VALUES(?);
        } [list param1 String $text]
      }
      update {
        set text [appendArgs "updated: " [getSomeText $random $count]]

        sql execute $db {
          UPDATE t1 SET y = ? WHERE x NOT IN (
            (SELECT MIN(x) FROM t1), (SELECT MAX(x) FROM t1)
          );
        } [list param1 String $text]
      }
      delete {
        sql execute $db {
          DELETE FROM t1 WHERE x = (SELECT MIN(x) FROM t1);
        }
      }
    }
  }
}

###############################################################################

proc getChangeSetFileName { {suffix ""} } {
  return [file join \
      [getTemporaryDirectory] [appendArgs changes $suffix .bin]]
}

###############################################################################

proc arrayToList { varName } {
  upvar 1 $varName array

  if {![array exists array]} {
    error [appendArgs \" $a "\" isn't an array"]
  }

  set result [list]

  foreach name [lsort -integer [array names array]] {
    lappend result $array($name)
  }

  return $result
}

###############################################################################

proc createByteArray { list } {
  set length [llength $list]
  set result [object create -alias System.Byte\[\] $length]

  for {set index 0} {$index < $length} {incr index} {
    set element [lindex $list $index]
    set value [object invoke -create Byte Parse $element]
    $result SetValue $value $index
  }

  return $result
}

###############################################################################

runTest {test session-1.1 {basic session extension usage} -setup {
  setupDb [set fileName(0) session-1.1.db]

  set fileName(1) [getChangeSetFileName 1]
  set fileName(2) [getChangeSetFileName 2]

  cleanupSomeText
} -body {
  createTheSchema $db
  makeSomeChanges $db [list insert] true

  set connection [getDbConnection]

  set session [$connection -alias CreateSession main]
  $session AttachTable null

  makeSomeChanges $db [list insert update delete] true

  set byteArray null
  set changeSet(1) [$session -alias CreateChangeSet byteArray]
  set rawData [createByteArray [arrayToList byteArray]]
  object removeref $rawData

  set stream(1) [object create -alias \
      System.IO.FileStream $fileName(1) Create Write]

  $stream(1) Write $rawData 0 [$rawData Length]
  $stream(1) Flush; $stream(1) Close

  set stream(2) [object create -alias \
      System.IO.FileStream $fileName(2) Create Write]

  set changeSet(2) [$session -alias CreateChangeSet $stream(2)]
  $stream(2) Flush; $stream(2) Close

  list [expr {[file size $fileName(1)] > 0}] \
      [string equal [readFile $fileName(1)] [readFile $fileName(2)]]
} -cleanup {
  cleanupSomeText

  unset -nocomplain changeSet stream rawData byteArray session

  freeDbConnection

  unset -nocomplain connection

  cleanupFile $fileName(2)
  cleanupFile $fileName(1)

  cleanupDb $fileName(0)

  unset -nocomplain db fileName
} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
System.Data.SQLite SQLiteInterop\
defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result \
{True True}}

###############################################################################

runTest {test session-1.2 {session change set enumeration} -setup {
  setupDb [set fileName session-1.2.db]

  cleanupSomeText
} -body {
  createTheSchema $db
  makeSomeChanges $db [list insert insert insert] false

  set connection [getDbConnection]

  set session [$connection -alias CreateSession main]
  $session AttachTable null

  makeSomeChanges $db [list insert update delete] false

  set byteArray null
  set changeSet(1) [$session -alias CreateChangeSet byteArray]
  set rawData [createByteArray [arrayToList byteArray]]
  object removeref $rawData

  set changeSet(2) [$connection -alias CreateChangeSet $rawData]

  changeSetToString $changeSet(2) true
} -cleanup {
  cleanupSomeText

  unset -nocomplain oldValue newValue conflictValue
  unset -nocomplain changeSet rawData byteArray session

  freeDbConnection

  unset -nocomplain connection

  cleanupDb $fileName

  unset -nocomplain db fileName
} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
System.Data.SQLite SQLiteInterop\
defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result {TableName\
t1 NumberOfColumns 2 OperationCode Delete Indirect False PrimaryKeyColumns\
{[True, False]} OldValue 0 1 NewValue 0 <nullObject> ConflictValue 0\
<nullObject> OldValue 1 {"inserted: Alpha Bravo Charlie Delta Echo"} NewValue 1\
<nullObject> ConflictValue 1 <nullObject> TableName t1 NumberOfColumns 2\
OperationCode Update Indirect False PrimaryKeyColumns {[True, False]} OldValue\
0 2 NewValue 0 <nullObject> ConflictValue 0 <nullObject> OldValue 1 {"inserted:\
Foxtrot Golf Hotel India Juliet"} NewValue 1 {"updated: Uniform Victor Whiskey\
X-ray Yankee"} ConflictValue 1 <nullObject> TableName t1 NumberOfColumns 2\
OperationCode Update Indirect False PrimaryKeyColumns {[True, False]} OldValue\
0 3 NewValue 0 <nullObject> ConflictValue 0 <nullObject> OldValue 1 {"inserted:\
Kilo Lima Mike November Oscar"} NewValue 1 {"updated: Uniform Victor Whiskey\
X-ray Yankee"} ConflictValue 1 <nullObject> TableName t1 NumberOfColumns 2\
OperationCode Insert Indirect False PrimaryKeyColumns {[True, False]} OldValue\
0 <nullObject> NewValue 0 4 ConflictValue 0 <nullObject> OldValue 1\
<nullObject> NewValue 1 {"inserted: Papa Quebec Romeo Sierra Tango"}\
ConflictValue 1 <nullObject>}}

###############################################################################

rename createByteArray ""
rename arrayToList ""
rename getChangeSetFileName ""
rename makeSomeChanges ""
rename createTheSchema ""
rename changeSetToString ""
rename forDisplay ""
rename cleanupSomeText ""
rename getSomeText ""

###############################################################################

runSQLiteTestEpilogue
runTestEpilogue
