#! /usr/bin/env tclsh

package require nano

# Generate (derive) many accounts for every supplied seed to ensure we find
# all pending balances
set numAccountsPerSeed 500

proc prompt {message var {default ""}} {
	upvar $var data

	puts -nonewline "${message}: "
	flush stdout

	gets stdin data
	if {$data eq ""} {
		set data $default
	}

	return $data
}

# Prompt the user for input
puts "> Nano Offline Batch Sign"
puts ">> Ultimate Destination"
while true {
	prompt "Nano Address" ultimateDestination
	if {[catch {
		set ultimateDestinationPubKey [::nano::address::toPublicKey $ultimateDestination -hex -verify]
	} err]} {
		puts "Error: $err"
		continue
	}
	break
}

puts ">> Seeds (blank line when done)"
while true {
	prompt "Seed" seed
	if {[string length $seed] == 0} {
		break
	}

	if {[catch {
		# Verify the seed
		::nano::internal::generateKey [binary decode hex $seed] 0

		lappend seeds $seed
	} err]} {
		puts "Error: $err"
	}
}
set penultimateDestinationKey [::nano::internal::generateKey [binary decode hex [lindex $seeds 0]] 0]
set penultimateDestination    [::nano::address::fromPrivateKey $penultimateDestinationKey]

puts ">> Output File"
while true {
	prompt "Filename" outputFileName
	set outputFileName [file normalize $outputFileName]
	if {[catch {
		set outputFD [open $outputFileName w]
	} err]} {
		puts "Error: $err"

		continue
	}

	break
}

puts ">> Database File (Frontiers and Pending)"
while true {
	prompt "Filename" inputDBFileName
	if {[catch {
		set inputDBFD [open $inputDBFileName rb]
		zlib push gunzip $inputDBFD
	} err]} {
		puts "Error: $err"

		continue
	}

	break
}

puts ""
puts ">> Summary"
puts ">>> Destinations"
puts ">>>> Penultimate Destination: $penultimateDestination"
puts ">>>> Ultimate Destination:    $ultimateDestination"
puts ">>> Derived keys:             $numAccountsPerSeed * [llength $seeds]"
puts ">>> Output File:              $outputFileName"
puts ">>> Database File:            $inputDBFileName"
puts ""

puts ">> Loading Database File"
apply {{inputDBFD} {
	while true {
		gets $inputDBFD line
		if {[eof $inputDBFD] && $line eq ""} {
			break
		}

		set type [lindex $line 0]
		set args [lrange $line 1 end]

		switch -exact -- $type {
			"pending" - "::nano::account::addPending" {
				::nano::account::addPending {*}$args
			}
			"frontier" - "::nano::account::setFrontier" {
				::nano::account::setFrontier {*}$args
			}
		}
	}

	close $inputDBFD
}} $inputDBFD

set keys [list]
puts stderr ">> Generating [expr {[llength $seeds] * $numAccountsPerSeed}] keys"
foreach seed $seeds {
	set seed [binary decode hex $seed]
	for {set index 0} {$index < $numAccountsPerSeed} {incr index} {
		set key [::nano::internal::generateKey $seed $index]

		set accountPubKeys($key) [::nano::key::publicKeyFromPrivateKey $key -hex]
		lappend keys $key
	}
}

puts stderr ">> Working"
set resultingBlocks [list]
set iteration 0
puts -nonewline $outputFD "\["
set firstBlock true
while true {
	incr iteration

	set resultingBlocksCountBefore [llength $resultingBlocks]

	set index -1
	foreach key $keys {
		incr index

		set account [::nano::address::fromPrivateKey $key]

		if {($index % 100) == 0} {
			puts stderr ">>> $iteration/1/$index Processing $account"
		}

		foreach blockJSON [::nano::account::receiveAllPending $key $accountPubKeys($key)] {
			lappend resultingBlocks $blockJSON
			if {!$firstBlock} {
				puts -nonewline $outputFD ","
			}
			puts -nonewline $outputFD "$blockJSON"
			set firstBlock false
		}
	}

	set index -1
	foreach key $keys {
		incr index

		set account [::nano::address::fromPrivateKey $key]

		if {($index % 100) == 0} {
			puts ">>> $iteration/2/$index Processing $account"
		}

		set balance [::nano::account::getFrontier $account balance]
		if {$balance == 0} {
			continue
		}

		if {$account in [list $penultimateDestination $ultimateDestination]} {
			continue
		}

		set blockJSON [::nano::account::send $account $penultimateDestination $balance $key]
		if {!$firstBlock} {
			puts -nonewline $outputFD ","
		}
		puts -nonewline $outputFD "$blockJSON"
		set firstBlock false
	}

	set resultingBlocksCountAfter [llength $resultingBlocks]
	if {$resultingBlocksCountBefore == $resultingBlocksCountAfter} {
		break
	}
}

if {[::nano::address::toPublicKey $penultimateDestination] ne [::nano::address::toPublicKey $ultimateDestination]} {
	set balance [::nano::account::getFrontier $penultimateDestination balance]
	set blockJSON [::nano::account::send $penultimateDestination $ultimateDestination $balance $penultimateDestinationKey]
	if {!$firstBlock} {
		puts -nonewline $outputFD ","
	}
	puts -nonewline $outputFD "$blockJSON"
}

puts $outputFD "\]"

puts ">> Done !"
