/*
**      Newton Developer Technical Support Sample Code
**
**      SoupTour v4 - a quick walk through the soup features, 2.0 Savvy
**
**      by Bob Ebert, David Levy and Kent Sandvik, Newton Developer Technical Support
**
**      Copyright  1993-1995 by Apple Computer, Inc.  All rights reserved.
**
**      You may incorporate this sample code into your applications without
**      restriction.  This sample code has been provided "AS IS" and the
**      responsibility for its operation is 100% yours.  You are not
**      permitted to modify and redistribute the source as "DTS Sample Code."
**      If you are going to re-distribute the source, we require that you
**      make it clear in the source that the code was descended from
**      Apple-provided sample code, but that you've made changes.
*/


// To use:
//	Copy/Paste into NTK inspector.
//	Select blocks of code.
//	Hit ENTER to evaluate.


// create a prototypical soup entry. This should really be a
// constant. This is used so that all pizza soup entries will
// share the same frame map, thus saving space.

kSoupEntry := {name: nil, xtension: nil, crust: nil, size: nil,
					toppings: nil, cost: nil};

// create a simulated app symbol for the soup xmit functions.
kAppSymbol := '|SoupTour:PIEDTS|;

// now a function to return a new entry:

NewPizza := func(name, xtension, crust, size, toppings, cost)
begin
	local newPizza := Clone(kSoupEntry) ;
	newPizza.name := name;
	newPizza.xtension := xtension ;
	newPizza.crust := crust ;
	newPizza.size := size ;
	newPizza.toppings := toppings ;
	newPizza.cost := cost ;
	// return the pizza
	newPizza ;
end;


// get a reference to the RAM store
// as a rule you would use unionSoup
// to get at the combination of RAM and card
theStore:= getStores()[0];

// create a soup with four indexes based on different slots
thePizzas:= theStore:CreateSoupXMit("pizzaSoup", 
[ {structure: 'slot, path: 'name, type: 'string},
  {structure: 'slot, path: 'size, type: 'int},
  {structure: 'slot, path: 'crust, type: 'symbol},
  {structure: 'slot, path: 'cost, type: 'real}],
kAppSymbol);

// add some entries to my soup, I could omit or add slots at this stage
thePizzas:AddXmit(call newPizza with ("Keith", 2242, 'deep, 12,
					["cheese","pepperoni","sausage"], 12.75), kAppSymbol);

// more of the same for a little while
thePizzas:AddXmit(call newPizza with ("Nige", 2241, 'thin, 22,
					["cheese","pepperoni","sausage","pineapple"], 19.25), kAppSymbol);

thePizzas:AddXmit(call newPizza with ("Billy Boy", 2542, 'deep, 16,
					["cheese","pepperoni"], 9.95), kAppSymbol);

thePizzas:AddXmit(call newPizza with ("Fiona", 2530, 'thin, 10,
					["cheese","pepperoni","sausage","anchovies"], 11), kAppSymbol);

thePizzas:AddXmit(call newPizza with ("Entropy Dave", 2242, 'thin, 12,
					["cheese","dead animal","heat"], 15.35 ), kAppSymbol);

// lets see what indexes are installed
thePizzas:GetIndexes();

// an example query against a symbol slot
myCrustCursor := thePizzas:Query({indexPath: 'crust});

// what have we gotten, check what the cursor points to
myCrustCursor:Entry();

// advance to the next one
myCrustCursor:Next();

// and the next one as well, notice that the symbols were
//  kept indexed alphabetically
myCrustCursor:Next();

// a reset puts us back to where we started
myCrustCursor:Reset();

// lets see just how many pizzas have crusts
myCrustCursor:CountEntries()

// we can go straight to the first item which matches our key
// criterion
myCrustCursor:GotoKey('thin);


// another useful query may be to find all people that
// are into thin crust pizza.

myThinCursor := thePizzas:Query({indexpath: 'crust,
	 beginKey: 'thin, endKey: 'thin});

// count 'em
myThinCursor:CountEntries();

// get the last one
myThinCursor:ResetToEnd();

// back up one
myThinCursor:Prev()

// and again
myThinCursor:Prev()

// now there are no more
myThinCursor:Prev()

// use beginKey/endKey to limit the range of a cursor in an index.


// you can use a similar technique to get a range of
// numbers, like those who like their pizza between 10 and 16 dollars...
myCheapCursor := thePizzas:Query({indexPath: 'cost,
	 beginKey: 10.0, endKey: 16.0});

// lets get a list of people who like cheap pizzas
MapCursor(myCheapCursor, func(entry) entry.name);


// in this case we set up another cursor against a different
// indexed slot, and then supply numeric input to the the
// GotoKey method
mySizeCursor := thePizzas:Query({indexPath: 'size});
mySizeCursor:GotoKey(16);


// and naturally we can do much the same thing with string keys
myNameCursor := thePizzas:Query({indexpath: 'name});
myNameCursor:Entry();

myNameCursor:GotoKey("Fiona");


// here we make a query against a series of words which we
// might want find in a frame.  Notice that we get a nil
// cursor back, since the system requires every word to be
// present in order for a match
myHotCursor := thePizzas:Query({words: ["heat", "dust", "entropy"]});
myHotCursor:Entry();

// sure enough, there are no matches
myHotCursor:CountEntries();

// here of course it works
myHotCursor:= thePizzas:Query({words: ["heat", "entropy"]});
myHotCursor:CountEntries();
	
myHotCursor:Entry();


// More useful, a simple way to extract all the pepperoni entries
// we also use the 'name index to get the pepperoni lovers in alphabetical order
myPepperoniCursor := thePizzas:Query({indexPath: 'name, words: ["pepperoni"]});
myPepperoniCursor:CountEntries();

// also a simple example of moving through the cursor
myPepperoniCursor:Move(3);

// NOTE: this move is an O(n) (order n) move. That is
// you may think it goes to the item 3 places away
// really quickly, but in reality, the above statement
// is almost equivalent of doing 3 seperate moves!


// here we set up a query which will pass every entry to the 
// function defined in validTest.  If the test returns non-nil
// then the entry is counted as a match.  In this case it
// checks for people whose extension starts with 22
myXtnCursor := thePizzas:Query({validTest: func (entry)
	begin 
		if floor(entry.xtension/100.0)=22 then 1 else nil
	end});

// look at the first few entries
myXtnCursor:Entry();

myXtnCursor:Next();

// next we add an element to the toppings array
AddArraySlot(myXtnCursor:Entry().toppings, "chocolate");

myXtnCursor:Entry();


// this effectively does a revert to the last saved entry,
// so the chocolate element is now gone
EntryUndoChanges(myXtnCursor:Entry());
myXtnCursor:Entry();


// however since Nige likes chocolate, we will add it 
// permanently by making the change and saving it by 
// calling EntryChangeXmit()
// the curious may notice the system adding slots to the 
// entries as they are manipulated
AddArraySlot(myXtnCursor:Entry().toppings, "chocolate");
EntryChangeXmit(myXtnCursor:Entry(), kAppSymbol);
myXtnCursor:Entry();


// lastly, here is a simple traversal of soup entries to
// calculate the total tip on the pizzas
CalcTip := func() 
	begin
	// here MapCursor applies each element returned by the query
	// call, in this call all entries, to a function which
	// returns a tip on each pizza
	
	// these returned tips are collected into an array which the
	// foreach loop runs through to accumulate the total tip into
	// theTip
		local theTip := 0 ;
		foreach tip in MapCursor(thePizzas:Query({}), 
				func (entry) (entry.cost * 0.15)) do
			theTip:= theTip + tip;
		theTip ;
	end

// this will print the tip rounded appropriately
FormattedNumberStr(call calcTip with (), "%.2f");

// NOTE: you really need to do more than this if you
//       want to print out monitary units in a locale
//		   independant way, see the Newton Programmers Guide for more info

// Finally, remove our dear soup that we have used for this experiment.

// First check out if the soup is still there, should be
GetStores()[0]:GetSoupNames();

// Get access to the soup itself
thePizza := GetStores()[0]:GetSoup("pizzaSoup");

// Remove Myself from the store, if nil returned things are fine
thePizza:RemoveFromStoreXmit(kAppSymbol);

// Check out if the soup is still there?
GetStores()[0]:GetSoupNames();

// now be really nice and nil out the cursor variables
// and soup variables.
// this is what you often MUST do when you quit an App, or else
// you'll leave lots of large objects in the heap.

thePizzas := nil ;
myCrustCursor := nil ;
myThinCursor := nil ;
myCheapCursor := nil ;
mySizeCursor := nil ;
myHotCursor := nil ;
myPepperoniCursor := nil ;
myXtnCursor := nil ;

// are we having fun yet?


// example of how to dump soup contents
DumpNameSoup := func()
begin
	local theSoup, c, val;

	// get soup	
	theSoup := GetUnionSoup("Names");

	// create a cursor (no index spec means use default order, all entries)
	local c := theSoup:Query({});
	
	// while valid entries, dump the name information out
	val := c:Entry();
	while val do
		begin
			if(val.name.class = 'person) then
			begin
				// modify this as you wish
				print(val.name.last);
				print(val.name.first);
				print(val.city);
				print("\n");
			end;
			val := c:Next();
		end
end; // big end

call DumpNameSoup with ();


// example of how to create an empty soup (delete all entries if it exists)

CreateEmptySoup := func(store, soupName, indexes)
begin
	local s := store:GetSoup(soupName);		// get access to the soup
	
	if s then		// if not NIL
		s:RemoveAllEntries();					// purge
	else
		s := store:CreateSoup(soupName, indexes);
	
	return s;		// return the soup again
end;

call CreateEmptySoup with (GetStores()[0], "Test:PIEDTS",
					       '[{structure: slot, path: theSlotName, type: string}]);



