module View;

(****
* Module View contains all the operations in the menu item, View, except those that are 
* specific to student and instructor versions. It also contains the layer information.
*)

from LectureDB import CurrentSlide, EclassML, EndIndex, GlobalSlideSetup, LastClicked, Lecture, NumberOfSlides, OutlineVisibles, PreviousState, Source, Slide, SlideNumber, SlideName, SequenceItemBlock, SourcePool, SlideVisibles, StartIndex, SlideData, SlideSetup, UserWorkSpace;

export Coord, Enabler, Layer, PrivateNotes, PrivateLayer, PublicLayer, Shape;

operation GoTo is
     inputs:  selectedSlide:Slide and lecture:Lecture;
     outputs:  lecture':Lecture;
     preconditions:  exists (s in lecture.slides) (s.number = selectedSlide.number) and
                     (s.name = selectedSlide.name);
     postconditions:  lecture'.myCurrentPosition = selectedSlide.number;
     description: (* For this operation to occur, there must exist a slide in the lecture
                     such that the selected slide's name and number from the "GoTo" window
                     are equal to those of the slide that exists in the lecture. Then, the
                     position of myCurrentPosition object must change to the matching number
                     of the newly selected slide. Please note that the clsCurrentPosition
                     in not in this operation. We presume that the instructor's
                     clsCurrentPosition is always equal to his/her myCurrentPosition. *);
end GoTo;

operation GoToNextSlide is
     inputs:  lecture:Lecture;
     outputs:  lecture':Lecture;
     preconditions:  lecture.myCurrentPosition < lecture.total;
     postconditions:  lecture'.myCurrentPosition = lecture.myCurrentPosition + 1;
     description: (* This operation can occur if and only if the current position isn't at
                     the end of the slides. *);
end GoToNextSlide;

operation GoToPrevSlide is
     inputs:  lecture:Lecture;
     outputs:  lecture':Lecture;
     preconditions:  lecture.myCurrentPosition > 1;
     postconditions:  lecture'.myCurrentPosition = lecture.myCurrentPosition - 1;
     description: (* This operation can occur if and only if the current position isn't at 
                     the beginning of the slides. *);
end GoToPrevSlide;

operation GoToFirstSlide is
     inputs:  lecture:Lecture;
     outputs:  lecture':Lecture;
     preconditions:  exists (s in lecture.slides) (s.number = 1);
     postconditions:  lecture'.myCurrentPosition = 1;
     description: (* The current slide position is set to 1 for the first slide of the
                     lecture. *);
end GoToFirstSlide;

operation GoToLastSlide is
     inputs:  lecture:Lecture;
     outputs:  lecture':Lecture;
     preconditions:  exists (s in lecture.slides) (s.number = lecture.total);
     postconditions:  lecture'.myCurrentPosition = lecture.total;
     description: (* The current slide position is set to the last slide number of the
                     lecture. *);
end GoToLastSlide;

object Layer is
     components:  enable:Enabler and shapes:Shape*;
     description: (* These are the basic components of Public and Private layers. Enabler
                     indicates whether the layer is on and Shape* is a "list" that stores
                     all the objects drawn on that layer. *);
end Layer;

object Enabler is boolean;

object Shape is x:Coord and y:Coord;

object Coord is integer;

object PublicLayer inherits from Layer;

object PrivateLayer inherits from Layer;

object PrivateNotes is
     components: lecture:Lecture and private:PrivateLayer;
     description:  (* This layer is a combination of the private layer and current slide *);
end PrivateNotes;

operation ViewPublic is
     inputs:  public:PublicLayer and private:PrivateLayer (* and AccessRights *);
     outputs:  public':PublicLayer and private':PrivateLayer;
     preconditions:  private.enable = true and public.enable = false
                     (* and AccessRights = IC *);
     postconditions:  private'.enable = false and public'.enable = true;
     description: (* Access must be given for the student to view the public layer. Once
                     the public layer is selected, the private layer turns off. *);
end ViewPublic;

operation ViewPrivate is
     inputs:  public:PublicLayer and private:PrivateLayer;
     outputs:  public':PublicLayer and private':PrivateLayer;
     preconditions:  private.enable = false;
     postconditions:  private'.enable = true and public'.enable = false;
     description: (* The public layer must be turned off for the private layer to be
                     turned on. *);
end ViewPrivate;

operation Expand is
	inputs: lect:Lecture, si:SequenceItemBlock, sourcePool:SourcePool, 
		outlineVisibles:OutlineVisibles, slideVisibles:SlideVisibles, setup:GlobalSlideSetup;
	outputs: lect':Lecture, sourcePool':SourcePool, outlineVisibles':OutlineVisibles,
		 slideVisibles':SlideVisibles;

	description: (* Clicking on a SequenceItem that contains a collapsed sequence expands 
			that sequence and toggles its Expanded and Collapsed boolean state flags. *);

	precondition: 
	(
		((si in slideVisibles.eML.siBlocks) or (si in outlineVisibles.eML.siBlocks))
		(* SequenceItemBlock si is in slideVisibles or outlineVisibles. *)
		
			and
			
		(si.lastClicked = true)
		
		
			and
			
		exists (sb in sourcePool.eML.seqBlocks)
		(
		  (sb.startIndex > si.startIndex)
		  		     
		  		and 
		  		     	
		  (sb.startIndex < si.endIndex)
		  
		  		and
		  			
		  (sb.expanded = false and sb.collapsed = true)
		)
		(* There is a collapsed sequenceBlock sb within SequenceItemBlock si. *)
	);
	
	postcondition:
	(
		exists (sb in sourcePool.eML.seqBlocks)
		(
		  (sb.startIndex > si.startIndex)
		  		     
		  		and 
		  		     	
		  (sb.startIndex < si.endIndex)
		  
		  		and
		  			
		  (sb.expanded = false and sb.collapsed = true)
		)
		(* Repeated from preconditions to get sb. *)			
		
			and
		
		exists (slideToExpand in lect.slides)
		(
		  (sb.startIndex > slideToExpand.data.startIndex)
			
				and
			
		  (sb.startIndex < slideToExpand.data.endIndex)
		)
		(* startIndex of sb is used to locate slideToExpand. *)
		
			and

		(not (sb in sourcePool'.eML.seqBlocks))
		(* The collapsed sb is no longer in sourcePool' since it is replaced by the 
		   expanded sb'. Repeat this condition for outlineVisibles' and slideVisibles'
		   so sb is absent from those as well. *)
		   
			and
		
		exists (sb' in sourcePool'.eML.seqBlocks) 
		(
		  (sb'.expanded = true and sb'.collapsed = false)
		  (* All the other component values for sb' are identical to sb. *)
		)
		(* Expanded sb' is now in sourcePool'. Repeat this condition for outlineVisibles'
		   and slideVisibles' so sb' is present in those as well. *)
			
			and
			
		if( ((setup.scrollable = false) and (slideToExpand.data.maxPos >= sb'.endPos))
		     				
		     				  or
		     				
		     			(setup.scrollable = true) 
		) then 
		(  
		  (slideToExpand.data.endPos = slideToExpand.data.endPos + sb'.size)
		  	
		  				  and
		  		
		   (slideToExpand.data.size = slideToExpand.data.size + sb'.size)
		)
		(* If the slide setup is not scrollable but the expanded sequenceBlock fits on the 
		   slide then the size of the expanded sequenceBlock is added to the endPos and size 
		   of the slide. The same holds true if the slide setup is scrollable regardless of 
		   what the slide's maxPos is. A scrollbar appears on the right edge of the slides 
		   window if maxPos is exceeded. *)
		   
		   	and
		   	
		if(		       (setup.scrollable = false) 
		
						  and 
						
				(sb'.endPos > slideToExpand.data.maxPos)
		) then
		(
		  (slideToExpand.data.endPos = slideToExpand.data.endPos + sb'.size)
		  	
		  				  and
		  		
		  	(slideToExpand.data.size = slideToExpand.data.size + sb'.size)	
		
						  and
				
		  (Slidify(lect', slideToExpand, sourcePool', slideVisibles', outlineVisibles', setup))
		)
		(* If the slide setup is not scrollable and the expanded sequenceBlock doesn't fit
		   then increase the size of slideToExpand by the size of the sequenceBlock. This 
		   means endPos will exceed maxPos which is not the desired end result but that 
		   condition is resolved when slidify is true and new slides are inserted. *)
	);
end Expand;


function Slidify(lect:Lecture, slideToExpand:Slide, sourcePool:SourcePool, slideVisibles:SlideVisibles, 
			outlineVisibles:OutlineVisibles, setup:SlideSetup) = 
	(
		exists (offset:integer)
		  (offset = (slideToExpand.data.endPos - slideToExpand.data.maxPos) 
		  		/ setup.maxSize)
		(* The offset in grid units is calculated by subtracting slideToExpand maxPos from 
		   slideToExpand endPos. This will not produce a negative value since endPos being
		   larger than maxPos is a precondition for this function. The difference between 
		   endPos and maxPos is then divided by setup.maxSize to get the number of slides to 
		   be inserted. *)
		   
		   	and
		  	
		forall (i:integer | (i > slideToExpand.number) and (i <= #(lect.slides)))
		  (lect.slides[i + offset] = lect.slides[i])
		(* All the existing slides after the slideToExpand need to be shifted over to make 
		   room for the new ones. The start and endPositions of all the tag blocks affected 
		   need to be increased by the difference between slideToExpand.endPos and 
		   slideToExpand.maxPos. *)
		   
		   	and
		   	
		forall (i:integer | (i > slideToExpand.number) and (i <= slideToExpand.number + offset))
		(
		  (* New Slides must be generated to fill the space left by shifting the existing
		     slides. Every new slide is maxSize by default so it's start and endPos can be
		     derived using simple arithmetic. *)
		        			
		  (* To derive each new Slide's start and endIndexes the tag block with the highest
		     endPos in the new Slide's data must be identified and its endIndex retrieved. A 
		     SlideBreak must then be inserted after that tag block. The endIndex of 
		     that SlideBreak becomes the endIndex of the new Slide. Adding one to that gives
		     us the startIndex of the next newS lide and so on. No new changes need to 
		     be made to new Slide's start and endPos each time a SlideBreak is added 
		     because SlideBreaks don't take up any space in the Slides window. *) 
		)
 
	); (* End Slidify. *)

operation Collapse is
	inputs: lect:Lecture, si:SequenceItemBlock, sourcePool:SourcePool, 
		outlineVisibles:OutlineVisibles, slideVisibles:SlideVisibles, setup:GlobalSlideSetup;
	outputs: lect':Lecture, sourcePool':SourcePool, outlineVisibles':OutlineVisibles,
		 slideVisibles':SlideVisibles;

	description: (* Clicking on a SequenceItemBlock that contains a collapsed sequence expands 
			that sequence and toggles its Expanded and Collapsed boolean state flags. *);

	precondition: 
	(
		((si in slideVisibles.eML.siBlocks) or (si in outlineVisibles.eML.siBlocks))
		(* SequenceItemBlock si is in slideVisibles or outlineVisibles. *)
		
			and
			
		(si.lastClicked = true)
		
		
			and
			
		exists (sb in sourcePool.eML.seqBlocks)
		(
		  (sb.startIndex > si.startIndex)
		  		     
		  		and 
		  		     	
		  (sb.startIndex < si.endIndex)
		  
		  		and
		  			
		  (sb.expanded = false and sb.expanded = true)
		)
		(* There is an expanded sequenceBlock sb within SequenceItemBlock si. *)
	);
	
	postcondition: 
	(
		exists (sb in sourcePool.eML.seqBlocks)
		(
		  (sb.startIndex > si.startIndex)
		  		     
		  		and 
		  		     	
		  (sb.startIndex < si.endIndex)
		  
		  		and
		  			
		  (sb.expanded = false and sb.collapsed = true)
		)
		(* Repeated from preconditions to get sb. *)
		
			and		

		(not (sb in sourcePool'.eML.seqBlocks))
		(* The expanded sb is no longer in sourcePool' since it is replaced by the 
		   collapsed sb'. Repeat this condition for outlineVisibles' and slideVisibles'
		   so sb is absent from those as well. *)
		   
			and
		
		exists (sb' in sourcePool'.eML.seqBlocks) 
		(
		  (sb'.expanded = false and sb'.collapsed = true)
		  (* All the other component values for sb' are identical to sb. *)
		)
		(* Expanded sb' is now in sourcePool'. Repeat this condition for outlineVisibles'
		   and slideVisibles' so sb' is present in those as well. *)
		
			and
			
		DeleteSlides(lect', sb'.startIndex, sb'.endIndex)
		(* Delete the collapsed SequenceBlock from the slides. *)
	);
end Collapse;
		   

function DeleteSlides(lect:Lecture, startIndex:StartIndex, endIndex:EndIndex) =
	(forall (slide:Slide) 
	  ( (slide in lect.slides) iff ((slide.data.endIndex < startIndex) or 
	  				(slide.data.startIndex > endIndex)) )
		(* Slides whose data lies between the specified start and endIndexes are no longer 
		   in lect.slides. *)
	  );

end View;