// NewtWiki
//
// This is an auto-part which adds wiki-like capabilities to a Newton.
// NewtWiki adds automatic hypertext capabilities to Notes. Gesture to the
// right from any word and NewtWiki will take you to a note with that title.
// If no note exists for that title, it will create a new one. NewtWiki
// generates a backlist as you navigate. Gesture to the left, and you will
// navigate back through the backlist. The backlist is only populated with
// notes that you've navigated through via right gestures, so you can use it
// to go back after you follow a hyperlink but not if you manually select a new
// note.
//
// Limitations and features
// NewtWiki currently only navigates within folders. That is, you can't go from
// a note in the Unfiled folder to a note in the Misc folder. This allows for
// multiple "namespaces".
//
// Also, NewtWiki doesn't work fully with HyperNewt. If you have HyperNewt set to
// replace Notes, it will work. However, it will only navigate within a single folder/
// HyperNewt directory. That is, if you're currently in the Unfiled folder, and in a
// HyperNewt directory called "Foo", you can only navigate to other notes that are
// both unfiled and in the "Foo" directory.
//

InstallScript := func(partFrame)
begin
	// This is the installscript stuff for the bookmark app
	local mainLayout := partFrame.theForm;
	partFrame.removeFrame := 
		mainLayout:NewtInstallScript(mainLayout);
		
	// The rest of this adds the new slots to Notes
	local notes := GetRoot().paperroll;
	notes:Close();
	
	notes.BackList := [];
	
	notes.NewWikiTitle := "";
	
	notes.AddToBackList := func(noteID)
		begin
			if debugOn then Print("Adding" && noteID && "to BackList");
			if noteID <> 0 then AddArraySlot(BackList, noteID);
		end;
	
	notes.GoBack := func()
		begin
			local iLen := Length(BackList);
			if iLen > 0 then
			begin
				// Grab the last item off of the list
				local lastNote := BackList[iLen-1];
				ArrayRemoveCount(BackList, iLen-1, 1);
				:OpenNoteById(lastNote);
			end;
		end;
		
	notes.OpenNoteById := func(noteID)
		begin
			if debugOn then Print("OpenNoteById(" & noteID & ")");
		
			local soup := GetUnionSoup("Notes");
			
			local curs := soup:Query({indexPath: '_uniqueID, beginKey: noteID, endKey: noteID});
			local e := curs:Entry();
			if e then
				Print("Got a note!");
			else
				Print("Didn't get a note...");
			if e then :ShowFoundItem(e, {});
		end;
	
	notes.HandleBacklight := func()
		begin
			// Toggle the backlight, if it exists
			local result := Gestalt(kGestaltArg_HasBackLight);
			if result and result[0] then
			begin
				local bl := BackLightStatus();
				if bl then
					bl := nil;
				else
					bl := true;
				BackLight(bl);
			end;
			return true;
		end;
		
	notes.TitleReferenced := func(strTitle, curLabel)
		begin
//			if debugOn then Print("checking" && strTitle);

			local soup := GetUnionSoup("Notes");
			local curs := soup:Query({words: [strTitle]});
			
			local e := curs:Entry();
			
//			if debugOn then Print("Checking entry for" && strTitle & "...");
			
			while e do
			begin
				// If the string isn't in the title, it's a reference...
				if e.labels = curLabel then
				begin
					if e.title exists then
					begin
						if StrFilled(e.title) then
						begin
							if StrPos(e.title, strTitle, 0) = nil then
							begin
//								if debugOn then Print("TitleReferenced(" & strTitle & ") found a reference");
								return true;
							end;
						end;
					end;
				end;
				e := curs:Next();
			end;
//			if debugOn then Print("TitleReferenced(" & strTitle & ") didn't find a reference");
			return nil;
		end;
		
	notes.HandleGenOrphanList := func()
		begin
//			if debugOn then Print("HandleGenOrphanList");
			
			// Get our current label, if we can...
			local curLabel := nil;
			if entries exists then curLabel := entries[0].labels;
			
			// Generate an orphan list for this namespace
			// Generate a list of orphaned notes
			local strNoteText := "No orphaned notes!";
			
			local soup := GetUnionSoup("Notes");
			local curs := soup:Query({});
			
//			if debugOn then Print("Checking" && curs:CountEntries() && "entries");
			
			local e := curs:Entry();
			if e then
				strNoteText := "";
			while e do
			begin
//				if debugOn then Print("Checking" && e.title);
				
				if e.labels = curLabel then
				begin				
					if e.title exists then
					begin
						if StrFilled(e.title) then
						begin
							// Just check the first word in the title...
							local fn := StrTokenize(e.title, $ );
							local strFirstWord := call fn with ();
							if :TitleReferenced(strFirstWord, curLabel) = nil then
								strNoteText := strNoteText & e.title & "\n";
							end;
					end;
				end;
					
				e := curs:Next();
			end;
			
//			if debugOn then Print("Done checking...");
			
			:MakeTextNote(strNoteText, true);
			
			// Get the note back from the soup
			local curs2 := soup:Query({indexPath:'timeStamp});
			curs2:ResetToEnd();
			e := curs2:Entry();
			e.title := "OrphanedNoteReport";
			// Ensure this gets copied to our folder...
			
			EntryChangeXmit(e, nil);
			
			// Under HyperNewt, this won't properly navigate to the new note. Thus, we do this...
			AddDeferredSend(self, 'TryOpenNote, ["OrphanedNoteReport"]);
			return true;
		end;
		
	notes.HandleURL := func(strURL)
		begin
			if debugOn then Print("HandleURL(" & strURL & ")");
			
			local newtscape := GetRoot().|NewtsCape:NewtsCape|;
			if newtscape <> nil then
				newtscape:getURL(strURL, nil);
			return true;
		end;
		
	notes.HandleSpecialGestureLinks := func(strWord)
		begin
			// This function handles special GestureLinks. So far, the only one that
			// it supports is the Backlight link.
			
			if StrEqual(strWord, "Backlight") then
				return :HandleBacklight();
				
			// Do we start with http://?
			if BeginsWith(strWord, "http://") then
				return :HandleURL(strWord);
				
			if BeginsWith(strWord, "NewtWikiOrphanedList") then
				return :HandleGenOrphanList();
				
			return nil;
		end;
		
	notes.CreateWikiFromTemplate := func(symbol)
		begin			
			// Look for a note with that title
			local soup := GetUnionSoup("Notes");
			
			local strName := "NewtWikiTemplate";
			
			local curs := soup:Query({words: [strName]});
	
			if debugOn then Print("Searching for" && strName);
				
			local e := curs:Entry();
			while e do
			begin
				if e.title <> nil then
				begin
					if e.class = symbol then
					begin
						if StrFilled(e.title) then
						begin
							if debugOn then Print("Checking" && e.title);
						
							if StrEqual(e.title, strName) then
							begin
								if debugOn then Print("Duplicating" && e.title);
							
								// Duplicate it...
								local store := if :GetAppPreferences().internalStore then
									GetStores()[0];
							   	else
							   		GetDefaultStore();
							   		
							   	local newEntry := allSoups.notes:DuplicateEntry(e, store);
							   	return true;
							end;
						end;
					end;
				end;
				e := curs:Next();
			end;
			return nil;
		end;
		
	notes.PickActionScript := func(itemSelected)
		begin		
			soup := GetUnionSoup("Notes");
			
			local strNoteText := "New Note";
			local bCreateNote := nil;
			
			// Get a list of data defs, and call the new function for the one we want...
			local i := 0;
			local defs := GetAppDataDefs('notes);
			foreach def in defs do
			begin
				if i = itemSelected then
				begin
					if def.MakeNewEntry exists then
					begin
						if debugOn then Print("Trying to create a" && def.name);
						
						// Try to create from a template
						if :CreateWikiFromTemplate(def.symbol) = nil then
						begin
							if StrEqual(def.name, "Note") = nil then
							begin
								local a := "a";
								local namechar := def.name[0];
								if (namechar = $a) or (namechar = $e) or (namechar = $i) or (namechar = $o) or (namechar = $u) then
									a := "an";
								strNoteText := "You need" && a && def.name && " template called 'NewtWikiTemplate' to create a new" && def.name;
							end;
							bCreateNote := true;
						end;
					end;
				end;
				i := i + 1;
			end;
			
			if bCreateNote then
			begin
				// @@bugbug -- For some reason, the whole MakeTextNote()/NewNote() combo isn't working for me. This is a hack workaround.
				// It also takes care of the problem in HyperNewt where MakeTextNote(..., true) won't open the note properly.
//				newNote := notes:MakeTextNote(strWord, nil);
//				newNote.title := strWord;
//				:NewNote(newNote, nil, nil);

				:MakeTextNote(strNoteText, true);
			end;
			
			// Get the note back from the soup
			local curs := soup:Query({indexPath:'timeStamp});
			curs:ResetToEnd();
			local e := curs:Entry();
			e.title := NewWikiTitle;
			// Ensure this gets copied to our folder...
			if entries exists then e.labels := entries[0].labels;
			
			EntryChangeXmit(e, nil);
			
			// Under HyperNewt, this won't properly navigate to the new note. Thus, we do this...
			AddDeferredSend(self, 'TryOpenNote, [NewWikiTitle]);
			NewWikiTitle := "";
		end;
		
	notes.CreateWikiNote := func(strWord, x, y)
		begin
			local pt := {left: x, top: y};
			local box := :GlobalBox();
			pt.top := pt.top - box.top;
			
			local pickItems := [];

			local defs := GetAppDataDefs('notes);
			foreach def in defs do
			begin
				AddArraySlot(pickItems, {item: "New" && def.name});
			end;
			
			NewWikiTitle := strWord;
			
			:PopupMenu(pickItems, pt);
			return;
		end;
		
	notes.TryOpenNote := func(strWord)
		begin
			// Look for a note with the title strWord
			local soup := GetUnionSoup("Notes");
			
			local curs := soup:Query({words: [strWord]});
	
			if debugOn then
				Print("Searching for" && strWord);
				
			local fn := nil;
			local strFirstWord := nil;
				
			local e := curs:Entry();
			while e do
			begin
				if e.title <> nil then
				begin
					if StrLen(e.title) > 0 then
					begin
						if debugOn then Print("Checking" && e.title);
					
						// Just check the first word in the title...
						fn := StrTokenize(e.title, $ );
						strFirstWord := call fn with ();
						if StrEqual(strFirstWord, strWord) then
						begin
							if debugOn then Print("Found note" && e.title);
							
							local bShow := true;
				
							if entries exists then
							begin
								if debugOn then Print("Current note _uniqueID:" && entries[0]._uniqueID & ", new note _uniqueID:" && e._uniqueID);
								if e._uniqueID <> entries[0]._uniqueID then :AddToBackList(entries[0]._uniqueID);				
								
								if debugOn then Print("Current note labels:" && SPrintObject(entries[0].labels) &", new note labels:" && e.labels);	
								
								// If the new item has a different label than ours, skip it...
								if entries[0].labels <> e.labels then 
								begin
									// Check that e.labels is a folder...
									if GetFolderStr(e.labels) then
										bShow := nil;	// It is. Don't show us.
									else
									begin
										// Nope. So long as we're the same folder...
										if entries[0].labels <> nil then
											bShow := nil;
									end;
								end;
							end;
	
							if bShow then
							begin
								:ShowFoundItem(e, {});
								return true;
							end;
						end;
					end;
				end;
				e := curs:Next();
			end;
			return nil;
		end;
		
	notes.OpenNote := func(strWord, x, y)
		begin
			// OpenNote opens a note with the title strWord. It only considers notes in the same namespace
			// (folder, or HyperNewt folder/directory combination). If it finds the note, it opens it. If
			// it doesn't, it creates a new one.
		
			if :HandleSpecialGestureLinks(strWord) then
				return;

			if :TryOpenNote(strWord) then
				return;
				
			// Simple stemming
			// @@bugbug -- This is English-specific, and flawed. It would be nice to do this in a dictionary-
			// based method...
			if debugOn then Print("Attempting some simple stemming of '" & strWord & "'");
			local len := StrLen(strWord);
			if EndsWith(strWord, "es") then
			begin
				if debugOn then Print("Stripping off the ending 'es'");
				if :TryOpenNote(SubStr(strWord, 0, len-2)) then
					return;
			end;
			if strWord[len-1] = $s then
			begin
				if debugOn then Print("Stripping off the ending 's'");
				if :TryOpenNote(SubStr(strWord, 0, len-1)) then
					return;
			end;
			
			
			///////////////////////////////////////////
			// This breaks new note generation!!!
			//
			// Be sure and comment out this return!
			// return;
			///////////////////////////////////////////
	
			// Doesn't find anything...
			if debugOn then Print("Didn't find anything, creating a new note" && strWord);
		
			:CreateWikiNote(strWord, x, y);
		end;
		
	notes.GetNoteGestureLinkSubStr := func(strText, startchar, count)
		begin
			// Check the beginning and the end. If either isn't whitespace, then
			// expand the string until we have either no more string or we have whitespace...
			
//			if debugOn then Print("GetNoteGestureLinkSubStr(" & strText & ", " & startchar & ", " & count & ")");
			
			local bGotStart := nil;
			local bGotEnd := nil;
			local len := StrLen(strText);
			
//			if debugOn then Print(strText && "is" && len && "chars long");
			
			// Start with the beginning...			
//			if debugOn then Print("Checking beginning of string");
			
			while bGotStart = nil do
			begin
//				if debugOn then Print("startchar is now" && startchar);
				if startchar > 0 then
				begin
					if IsWhiteSpace(strText[startchar-1]) then
						bGotStart := true;
					else
					begin
//						if debugOn then Print("Expanding string left");
						startchar := startchar - 1;
						count := count + 1;
					end;
				end
				else
					bGotStart := true;					
			end;
			
			// We don't expand to the end. We don't want to get punctuation & stuff...
			
//			if debugOn then Print("Returning" && SubStr(strText, startchar, count));
			
			return SubStr(strText, startchar, count);
		end;
		
	notes.GetNoteGestureLink := func(parent, x, y)
		begin
//			if debugOn then 
//			begin
//				Print("Checking another...");
//				Print(parent);
//			end;
			
		
			local hit := parent:PointToWord(x,y);
			local theWord := nil;
			
			if hit <> nil then
			begin
				theWord := :GetNoteGestureLinkSubStr(parent.text, hit.startchar, hit.endchar-hit.startchar);
				if debugOn then Print("Found the word!" && theWord);
				
				return theWord;
			end;
			
			// OK, check the children who live within this parent...
			local cvf := parent:ChildViewFrames();
			foreach child in cvf do
			begin
				theWord := :GetNoteGestureLink(child, x, y);
				if theWord then
					return theWord;
			end;
			return nil;
		end;
		
	notes.ViewGestureScript := func(unit, gestureKind)
		begin
			if debugOn then Print("ViewGestureScript(" & unit & ", " & gestureKind & ")");
			
//			if debugOn then 
//			begin
//				Print("Current note:");
//				Print(self);
//			end;
			
			
			if gestureKind = aeLine then
			begin
				if debugOn then Print("ViewGestureScript is handling the line...");
				
				// Only handle long strokes
				local x := GetPoint(firstX, unit);
				local y := GetPoint(firstY, unit);
				local x2 := GetPoint(finalX, unit);
				local y2 := GetPoint(finalY, unit);
				local dx := GetPoint(finalX, unit) - x;
				local dy := GetPoint(finalY, unit) - y;
				local dx2 := dx * dx;
				local dy2 := dy * dy;
				local dist_squared := dx2 + dy2;
				if debugOn then Print("Stroke distance:" && dist_squared);
				if dist_squared < 2000 then
					return nil;
				// Only handle strokes within 45 degrees of horizontal
				if dy2 > dx2 then
				begin
					if debugOn then Print("Skipping near vertical stroke");
					return nil;
				end;
					
				if debugOn then Print("Stroke extends from (" & x & "," & y & ") to (" & x2 & "," & y2 & ")");
					
				// If the stroke is to the left, go back
				if (dx < 0) and (dx < 40) then
				begin
					if debugOn then Print("Stroke is to the left (dx =" && dx & ") -- Going back");
					:GoBack();
					return true;
				end;
				
				local strWord := nil;
				
				strWord := :GetNoteGestureLink(self, x,y);
				
				if strWord <> nil then
				begin
					if debugOn then Print("GestureLinking to" && strWord);
				
					:OpenNote(strWord, x2, y2);
					return true;
				end
				else
				begin
					if debugOn then Print("Nuthin'");
				end;
			end;
			return nil;
		end;
end;

RemoveScript := func(removeFrame)
begin
	// This is the RemoveScript for the bookmark app
	local removeFrame := partFrame.removeFrame;
	
	call removeFrame.NewtRemoveScript with (removeFrame);

	// The rest of this removes the new slots from Notes
	local notes := GetRoot().paperroll;
	notes:Close();
	RemoveSlot(notes, 'NewWikiTitle);
	RemoveSlot(notes, 'BackList);
	RemoveSlot(notes, 'AddToBackList);
	RemoveSlot(notes, 'GoBack);
	RemoveSlot(notes, 'OpenNoteById);
	RemoveSlot(notes, 'HandleBacklight);
	RemoveSlot(notes, 'HandleURL);
	RemoveSlot(notes, 'TitleReferenced);
	RemoveSlot(notes, 'HandleGenOrphanList);
	RemoveSlot(notes, 'HandleSpecialGestureLinks);
	RemoveSlot(notes, 'TryOpenNote);
	RemoveSlot(notes, 'CreateWikiFromTemplate);
	RemoveSlot(notes, 'PickActionScript);
	RemoveSlot(notes, 'CreateWikiNote);
	RemoveSlot(notes, 'OpenNote);
	RemoveSlot(notes, 'GetNoteGestureLinkSubStr);
	RemoveSlot(notes, 'GetNoteGestureLink);
	RemoveSlot(notes, 'ViewGestureScript);
end