#!wish8.2 -f
#  'CBB' -- Check Book Balancer
#
#   balance.tcl -- Routines for balancing the account.
#
#  Written by Curtis Olson.  Started December 7, 1996.
#
#  Copyright (C) 1994 - 1999  Curtis L. Olson  - curt@me.umn.edu
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# $Id: balance.tcl,v 1.3 2000/01/05 13:04:12 curt Exp $


#------------------------------------------------------------------------------
# Procedures for balancing
#------------------------------------------------------------------------------

proc balance {} {
    global debits credits diff

    set debits 0.00
    set credits 0.00
    set diff [balanceCalcDiff]

    # create or bring forward window
    cbbWindow.bal

    # refresh the list
    balanceRefresh
}


# create window
proc cbbWindow.bal {} {
    global cbb debits credits diff

    if {[winfo exists .bal] == 1} {
	wm withdraw .bal
        wm deiconify .bal
	return                
    }

    toplevel .bal
    
    option add *font $cbb(dialog_font)

    wm title .bal "Balance ..."
    wm iconname .bal "Balance ..."
    frame .bal.frame -borderwidth 2 -relief raised
    frame .bal.frame.head1 -relief raised
    frame .bal.frame.head2 -relief raised
    frame .bal.frame.head3 -relief raised
    frame .bal.frame.sortkeys -relief raised
    frame .bal.frame.f -relief raised
    frame .bal.frame.bar -relief sunken

    label .bal.frame.head1.label -text "Statement Starting Balance = "
    entry .bal.frame.head1.entry -textvariable cbb(state_start) -relief sunken \
	    -width 17 -font $cbb(default_font)
    label .bal.frame.head2.label -text "Statement Ending Balance   = "
    entry .bal.frame.head2.entry -textvariable cbb(state_end) -relief sunken \
	    -width 17 -font $cbb(default_font)
    label .bal.frame.head3.label \
    	    -text "Debits = $debits  Credits = $credits  Difference = $diff"

    listbox .bal.frame.f.list -width 49 -height 15 -relief sunken \
	-exportselection false -takefocus 0 \
	-yscrollcommand ".bal.frame.f.scroll set" -font $cbb(fixed_font)

    bind .bal <KeyPress-Down> { .bal.frame.f.list \
	    yview scroll 1 units }
    bind .bal <Control-KeyPress-n> { .bal.frame.f.list \
	    yview scroll 1 units }

    bind .bal <KeyPress-Up> { .bal.frame.f.list \
	    yview scroll -1 units }
    bind .bal <Control-KeyPress-p> { .bal.frame.f.list \
	    yview scroll -1 units }

    bind .bal <KeyPress-Next> { .bal.frame.f.list \
	    yview scroll 1 pages }
    bind .bal <Control-KeyPress-v> { .bal.frame.f.list \
	    yview scroll 1 pages }

    bind .bal <KeyPress-Prior> { .bal.frame.f.list \
	    yview scroll -1 pages }
    bind .bal <Alt-KeyPress-v> { .bal.frame.f.list \
	    yview scroll -1 pages }

    scrollbar .bal.frame.f.scroll -takefocus 0 -relief flat \
	    -command { .bal.frame.f.list yview }
	

    button .bal.frame.bar.refresh -text "Refresh" -font $cbb(button_font) \
	    -takefocus 0 -command { balanceRefresh }

    button .bal.frame.bar.update -text "Update" -font $cbb(button_font) \
	    -takefocus 0 -command {
	balanceUpdateSelected .bal.frame.f.list
	clear_entry_area
	destroy .bal
    }

    button .bal.frame.bar.dismiss -text "Dismiss" -font $cbb(button_font) \
	    -takefocus 0 -command {
	clear_entry_area
	destroy .bal
    }	

    #
    # Create sort key buttons
    #
    button .bal.frame.sortkeys.chkno -font $cbb(fixed_header_font) \
       -text "Chk #" -foreground $cbb(head_color) -padx 2 -pady 0 \
       -command OnBalChkno
    button .bal.frame.sortkeys.date -font $cbb(fixed_header_font) \
       -text "Date" -foreground $cbb(head_color) -padx 2 -pady 0 \
       -command OnBalDate
    button .bal.frame.sortkeys.desc -font $cbb(fixed_header_font) \
       -text "Description" -foreground $cbb(head_color) -padx 2 -pady 0 \
       -command OnBalDesc
    button .bal.frame.sortkeys.amount -font $cbb(fixed_header_font) \
       -text "Amount" -foreground $cbb(head_color) -padx 2 -pady 0 \
       -command OnBalAmount

    pack .bal.frame -side top -fill both -expand 1
    pack .bal.frame.head1 -side top -fill x
    pack .bal.frame.head2 -side top -fill x
    pack .bal.frame.head3 -side top -fill x
    pack .bal.frame.sortkeys -side top -fill x
    pack .bal.frame.bar -side bottom -fill x
    pack .bal.frame.f -side top -fill both -expand 1

    pack .bal.frame.head1.label .bal.frame.head1.entry -side left -anchor w
    pack .bal.frame.head2.label .bal.frame.head2.entry -side left -anchor w
    pack .bal.frame.head3.label -side top -anchor w
    pack .bal.frame.f.scroll -side right -fill y
    pack .bal.frame.f.list -side left -fill both -expand 1
    pack .bal.frame.bar.refresh .bal.frame.bar.update .bal.frame.bar.dismiss \
	    -side left -expand 1 -fill x -padx 8 -pady 4
    pack .bal.frame.sortkeys.chkno \
         .bal.frame.sortkeys.date \
         .bal.frame.sortkeys.desc \
         .bal.frame.sortkeys.amount \
            -side left -expand 1 -fill x -padx 2 -pady 2 

    bind .bal.frame.head1.entry <FocusOut> {
        set diff [balanceCalcDiff]
        .bal.frame.head3.label configure \
    	     -text "Debits = $debits  Credits = $credits  Difference = $diff"
    }

    bind .bal.frame.head2.entry <FocusOut> {
        set diff [balanceCalcDiff]
        .bal.frame.head3.label configure \
    	     -text "Debits = $debits  Credits = $credits  Difference = $diff"
    }

    bind .bal.frame.f.list <Double-Button> {
 	set selection [.bal.frame.f.list curselection]
 	if [llength $selection] {
 	    acctSetDirty
 	    balanceUpdateList .bal.frame.f.list $selection
 	    set diff [balanceCalcDiff]
 	    .bal.frame.head3.label configure -text \
		    "Debits = $debits  Credits = $credits  Difference = $diff"
 	}
    }
    focus .bal.frame.head1.entry
}

#
# Callbacks for balance window sort keys
#

proc OnBalChkno {} {
   set tempList [.bal.frame.f.list get 0 end]
   foreach item $tempList {
      lappend keyedTempList [list [string range $item 2 6] $item]
   }
   set keyedTempList [lsort -command SortBalItems $keyedTempList]

   # Delete listbox contents
   .bal.frame.f.list delete 0 end

   # Copy newly sorted items back to list
   foreach item $keyedTempList {
      .bal.frame.f.list insert end [lindex $item 1]
   }
}

proc OnBalDate {} {
   set tempList [.bal.frame.f.list get 0 end]
   foreach item $tempList {
      lappend keyedTempList [list [string range $item 8 17] $item]
   }
   set keyedTempList [lsort -command SortBalItemsByDate $keyedTempList]

   # Delete listbox contents
   .bal.frame.f.list delete 0 end

   # Copy newly sorted items back to list
   foreach item $keyedTempList {
      .bal.frame.f.list insert end [lindex $item 1]
   }
}

proc OnBalDesc {} {
   set tempList [.bal.frame.f.list get 0 end]
   foreach item $tempList {
      lappend keyedTempList [list [string range $item 19 33] $item]
   }
   set keyedTempList [lsort -command SortBalItems $keyedTempList]

   # Delete listbox contents
   .bal.frame.f.list delete 0 end

   # Copy newly sorted items back to list
   foreach item $keyedTempList {
      .bal.frame.f.list insert end [lindex $item 1]
   }
}

proc OnBalAmount {} {
   set tempList [.bal.frame.f.list get 0 end]
   foreach item $tempList {
      lappend keyedTempList [list [string range $item 35 46] $item]
   }
   set keyedTempList [lsort -command SortBalItems $keyedTempList]

   # Delete listbox contents
   .bal.frame.f.list delete 0 end

   # Copy newly sorted items back to list
   foreach item $keyedTempList {
      .bal.frame.f.list insert end [lindex $item 1]
   }
}

proc SortBalItems {a b} {
   set aKey [lindex $a 0]
   set bKey [lindex $b 0]
   if {$aKey < $bKey} {
      return -1 
   } elseif {$aKey > $bKey} {
      return 1
   } else {
      return 0
   }
}  

# Sort using mm/dd/yy or dd.mm.yy key
# yy < 50 are converted to 2000+yy
# yy => 50 are converted to 1900+yy
proc SortBalItemsByDate {a b} {
   global cbb
   if {$cbb(date_fmt) == 1} {
      scan [lindex $a 0] %d/%d/%d mon day year
   } else {
      scan [lindex $a 0] %d.%d.%d day mon year
   }
   if {$year < 50} {incr year 2000} {incr year 1900}
   set aDate [format %04d%02d%02d $year $mon $day]

   if {$cbb(date_fmt) == 1} {
      scan [lindex $b 0] %d/%d/%d mon day year
   } else {
      scan [lindex $b 0] %d.%d.%d day mon year
   }
   if {$year < 50} {incr year 2000} {incr year 1900}
   set bDate [format %04d%02d%02d $year $mon $day]

   string compare $aDate $bDate
}

# refresh balance window
proc balanceRefresh {} {
    global cbb eng debit debits credit credits desc cleared check nicedate 
    global cutdesc amt key index

    set debits 0.00
    set credits 0.00
    set diff [balanceCalcDiff]

    # wipe the current contents
    .bal.frame.f.list delete 0 end

    # get the statement starting balance
    puts $eng "get_cleared_bal"; flush $eng
    gets $eng cbb(state_start)

    # load the list
    puts $eng "first_uncleared_trans"; flush $eng
    gets $eng result
    while { $result != "none" } {
        update_globals $result

	# handles case where we have a $debit and a $credit
	set amt [expr $credit - $debit]

	set cutdesc [string range $desc 0 14]

        puts $eng "get_current_index"; flush $eng
        gets $eng index
	
        # Don't forget to modify the OnBalxxxx methods if the format
        # is modified!
        .bal.frame.f.list insert end \
		[format "%1s %5.5s %10s %-15s %12.2f   %-9s %5s"\
		$cleared $check $nicedate $cutdesc $amt $key $index]

        if { "$cleared" == "*" } {
	    set debits [expr $debits + $debit]
	    set credits [expr $credits + $credit]
        }

        puts $eng "next_uncleared_trans"; flush $eng
        gets $eng result
    }

    # avoid something like 6.5999999999999
    set debits [format "%.2f" $debits]
    set credits [format "%.2f" $credits]

    set diff [balanceCalcDiff]

    .bal.frame.head3.label configure \
	    -text "Debits = $debits  Credits = $credits  Difference = $diff"; \
}


proc balanceCalcDiff {} {
    global cbb debits credits

    set value [expr $cbb(state_start) - $cbb(state_end) - $debits + $credits]
    return [format "%.2f" $value]
}


# called when user double clicks on a line in the balance window
proc balanceUpdateList args {
    global cbb eng debits credits diff

    set arglist [split $args]
    set list [lindex $arglist 0]
    set sel [lindex $arglist 1]

    if { $cbb(debug) } { puts "$list $sel" }

    set line [$list get $sel]
    set key [string range $line 50 60]
    set index [expr [string range $line 62 66] * 2 - 1]
    set tail [string range $line 2 end]
    set amt [string range $line 36 47]
    if { $cbb(debug) } { puts "amt = $amt" }

    $list delete $sel
    if { "[string range $line 0 0]" != "*" } {
        $list insert $sel "* $tail"
        puts $eng "select_trans $key"; flush $eng
        gets $eng result
	if { $cbb(debug) } { puts $result }
	set pieces [split $result "\t"]
	set debit [lindex $pieces 4]
	set credit [lindex $pieces 5]

	if { "$credit" == "" } {
	    set credit 0.00
	}

	if { "$debit" == "" } {
	    set debit 0.00
	}

	set debits [expr $debits + $debit]
	set credits [expr $credits + $credit]
    } else {
        $list insert $sel "  $tail"
        puts $eng "unselect_trans $key"; flush $eng
        gets $eng result
	set pieces [split $result "\t"]
	set debit [lindex $pieces 4]
	set credit [lindex $pieces 5]

	if { "$credit" == "" } {
	    set credit 0.00
	}

	if { "$debit" == "" } {
	    set debit 0.00
	}

	set debits [expr $debits - $debit]
	set credits [expr $credits - $credit]
    }

    # avoid something like 6.5999999999999
    set debits [format "%.2f" $debits]
    set credits [format "%.2f" $credits]

    update_line $index $key

    if { $cbb(debug) } { puts $line; puts $key; puts $index }
}


proc balanceUpdateSelected list {
    global cbb eng

    .status.line configure -text "Updating all cleared transactions."
    update

    acctSetDirty

    puts $eng "clear_trans"; flush $eng
    gets $eng result

    update_rest 0 00000000-00

    goto [listGetSize]
}
