Model subclass: #Spreadsheet
        instanceVariableNames: 'mySpreadSheet value '
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Spreadsheets'!


!Spreadsheet methodsFor: 'private'!

mySpreadSheet: newSpreadSheet
        "assign a spread sheet"

        mySpreadSheet := newSpreadSheet.!

value: aValue
        "update value and broadcast the change to dependencies."
        value := aValue.
        self changed: #value.! !

!Spreadsheet methodsFor: 'accessing'!

cell: whichCell
        "get the value of a cell"

        (whichCell > mySpreadSheet size) ifTrue:
                [ DialogView warn: 'The cell reference is out of range; defaulting to a value of 0.'. ^0. ].
        ^mySpreadSheet at: whichCell.!

mySpreadSheet
        ^mySpreadSheet.!

value
        ^value.! !

!Spreadsheet methodsFor: 'operations'!

cell: whichCell put: dataItem
        "fill a cell with some data"

        | index |

        index := (mySpreadSheet size) + 1.

        (whichCell > index) ifTrue:
                [ DialogView warn: 'You must use contiguous cells.  The cell will not be set as requested.'. ^False ].

        mySpreadSheet growToAtLeast: whichCell.
        mySpreadSheet at: whichCell put: dataItem.
        self updateValue.!

cells: anArray
        "assign mySpreadSheet a fixed array"
        mySpreadSheet := anArray.
        self updateValue.!

updateValue
        "force subclasses to implement this method"
        ^self subclassResponsibility.! !
"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

Spreadsheet class
        instanceVariableNames: ''!


!Spreadsheet class methodsFor: 'initializing'!

type: typeOfSheet
        "create a new spreadsheet of a specified type and return it"

        | newSheet view|

        newSheet := 0.          " initialize newSheet"
        view := SpreadsheetViewContainer new.   "create our attached view"
        (typeOfSheet = #sum) ifTrue:    "test whether we should build a summing spreadsheet"
                [ newSheet := SumSpreadsheet new.       " create the new spreadsheet"
                newSheet mySpreadSheet: Array new.      " initialize its spread sheet array to empty"
                view openOn: newSheet labeled: 'Sum'. ].        " attach its view"
        (typeOfSheet = #average) ifTrue:                        " likewise for averaging spreadsheets..."
                [ newSheet := AverageSpreadsheet new.
                newSheet mySpreadSheet: Array new.
                view openOn: newSheet labeled: 'Average'. ].
        (newSheet = 0)                                  "if the user asked for neither a sum nor average sheet, warn and abort"
                ifTrue: [  DialogView warn: 'Only #sum and #average types of spreadsheets are currently supported.'.  ^nil.] 
                ifFalse: [ ^newSheet. ]         " otherwise, we're ready to go"!

type: typeOfSheet with: sheetArray
        "create a new linked spreadsheet of a specified type and return it"

        | newSheet view|
        newSheet := 0.                                                  " initialize our new spreadsheet to 0 (failure)"
        view := SpreadsheetViewContainer new.           " create our new view"
        (typeOfSheet = #sum) ifTrue:                            " test which type of spreadsheet to create"
                [ newSheet := LinkedSumSpreadsheet new mySpreadSheet: sheetArray.       "create the spreadsheet with its spreadsheet array of linked sheets"
                view openOn: newSheet labeled: 'Linked Sum'. ]. " attach our new view"
        (typeOfSheet = #average) ifTrue:                        " likewise for averaging spreadsheets..."
                [ newSheet := LinkedAverageSpreadsheet new mySpreadSheet: sheetArray.
                view openOn: newSheet labeled: 'Linked Average'. ].
        (newSheet = 0)                                          " if the user picked neither averaging nor summing sheets, warn and abort"
                ifTrue: [  DialogView warn: 'Only #sum and #average types of spreadsheets are currently supported.' . ^nil. ] 
                ifFalse:        " otherwise, everything's ready to go..."
                        [ sheetArray do: [ :aSheet | aSheet addDependent: newSheet. ].  " create dependencies so we know when to update our own value"
                        newSheet updateValue.           " go ahead and initially update our value"
                         ^newSheet. ] .                         " return the prepared sheet"!

type: typeOfSheet with: sheet1 cell: cell1 with: sheet2 cell: cell2
        "create a new spreadsheet linked to two specific cells and return it"

        | newSheet view newArray|

        newSheet := 0.                          " initialize the new spreadsheet"
        view := SpreadsheetViewContainer new.   "create our new view"
        newArray := Array with: (SpreadsheetCell new sheet: sheet1; cell: cell1) with: (SpreadsheetCell new sheet: sheet2; cell: cell2).        "create a temporary array of spreadsheet/cell pairs"
        (typeOfSheet = #sum) ifTrue:    " test what type of spreadsheet to create"
                [ newSheet := LinkedCellSumSpreadsheet new mySpreadSheet: newArray.     " create the new summing sheet with its initial array of spreadsheet/cell pairs"
                view openOn: newSheet labeled: '2 Cell Sum'. ]. " attach the view"
        (typeOfSheet = #average) ifTrue:        " do likewise for averaging spreadsheets"
                [ newSheet := LinkedCellAverageSpreadsheet new mySpreadSheet: newArray.
                view openOn: newSheet labeled: '2 Cell Average'.] .
        (newSheet = 0)                  " if the user picked something other than summing or averaging, warn and abort"
                ifTrue: [  DialogView warn: 'Only #sum and #average types of spreadsheets are currently supported.'  ] 
                ifFalse: 
                        [ sheet1 addDependent: newSheet.        "create dependencies on the linked spreadsheets, so we know when to update our value"
                        sheet2 addDependent: newSheet.
                        newSheet updateValue.   " go ahead and update our initial value"
                        ^newSheet. ] .  " we're done"! !

Spreadsheet subclass: #SumSpreadsheet
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Spreadsheets'!


!SumSpreadsheet methodsFor: 'operations'!

updateValue
        "update value with the sum of the spreadsheet"

        value := 0.
        mySpreadSheet do:
                [ :cell | (cell = nil) ifFalse: [value := value + cell ]].
        self value: value.
        ^value.! !

Spreadsheet subclass: #AverageSpreadsheet
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Spreadsheets'!


!AverageSpreadsheet methodsFor: 'operations'!

updateValue
        "update value with the average value of the spreadsheet's cells"

        value := 0.
        mySpreadSheet do: [ :cell | (cell = nil) ifFalse: [value := value + cell ]].
        self value: value / (mySpreadSheet size).
        ^value.! !

Object subclass: #SpreadsheetCell
        instanceVariableNames: 'sheet cell '
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Spreadsheets'!


!SpreadsheetCell methodsFor: 'accessing'!

cell
        ^cell.!

cell: aCell
        cell := aCell.!

sheet
        ^sheet.!

sheet: aSheet
        sheet := aSheet.! !