#import "FlashDoc.h"

/* -------------------------------------------------------------------
enum {KEY, RES, NTextViews};

	RDR, Inc. 

	Objective-C source file for the class FlashDoc

	COPYRIGHT (C), 1991, RDR, Inc.
	ALL RIGHTS RESERVED.

	Responsible:			Approved:
	RDR:Ernest Prabhakar		

	Date:				Rev:
	1991-Oct-04			___

                               FlashDoc                               

	1. Introduction
	Implements a Deck of FlashCards on top of the Ordered List.
	Starts at the end of the deck and works its way down.
	Difficult words reappear more often.

	2. Revision History

	___	The starting point.	Ernest Prabhakar/1991-Oct-04


	3. Source Code

------------------------------------------------------------------- */

/* ------------------------- Import files ------------------------- */
#import <appkit/nextstd.h>
#import <objc/hashtable.h>

#import <appkit/Matrix.h>
#import <appkit/ScrollView.h>
#import <appkit/NXSplitView.h>
#import <appkit/Text.h>
#import <appkit/Font.h>
#import <appkit/Panel.h>
#import <appkit/ButtonCell.h>

/* ------------------------  Classes used  ------------------------ */
#import <objc/List.h>
#import <appkit/Panel.h>

#import "BasicApp.h"

/* --------------------------  Defines  --------------------------- */
#define ZAlloc(CL) [CL allocFromZone:[self zone]]
#define ClassKind(Obj,Class) [Obj isKindOf:[Class class]]

#define P_HIGH 104.0	// Cell Size
#define P_WIDE 274.0
#define P_MARG 7.0

#define ObjName(S) "Card_" #S
#define SetCell(I,T,S) [[[diff cellAt:0:I] setTitle:#T] setAction:@selector(S)]
#define SetDiffCell(I,N) SetCell(I,N, was ## N ## :)

/* -------------------------  Constants  -------------------------- */


/* ----------------------  Class variables  ----------------------- */

/* --------------------  Auxiliary Functions  --------------------- */
static id getScroll(NXRect *temp, id* tv, const char *font)
{
    id scroll = [[ScrollView alloc] initFrame:temp];
    *tv = [[Text allocFromZone:[scroll zone]] initFrame:temp];
    [[*tv setMonoFont:NO] setFont:[NXApp defaultFont:font]];
    [scroll setDocView:*tv];
    [[[scroll setBorderType:NX_BEZEL]
    	setVertScrollerRequired:YES]
       setBackgroundGray:NX_WHITE];
    return scroll;
}

static void alignText(Text *text, int align)
{
	[[text setAlignment:align] calcLine];
}

static void writeToCard(Card *card, id* tv)
{
	int i=2;	// Bottom Up
	NXStream *stream;

	while(i--) {
		stream=NXOpenMemory(NULL,0,NX_READWRITE);
		[tv[i] writeRichText:stream];
		[card setStringValueFrom:stream at:i];
		NXCloseMemory(stream, NX_FREEBUFFER);
		[card setIntValue:[tv[i] alignment] at:i];
	}
}


static void setText(id text, NXStream *stream, int align)
{
    if (stream) {
	[text readRichText:stream];
	NXCloseMemory(stream, NX_SAVEBUFFER);
	alignText(text, align);
    } else {
    	[text setText:"<Blank>"];
    }
}

static void readFromCard(Card *card, id* tv)
{
    int i=2;
    while(i--)
	setText(tv[i],[card stringStreamAt:i],[card intValueAt:i]);
}

static Text *newText(int half)
{
	Text *text;
	NXRect myRect;

	NXSetRect(&myRect, half*P_WIDE, 0.0, P_WIDE, P_HIGH );
	text = [[[Text alloc] initFrame:&myRect] setVertResizable:YES]; 
	[text setMarginLeft:P_MARG right:P_MARG top:P_MARG bottom:P_MARG];
	[text setMonoFont:NO];
	return text;
}

static id wrapViews(id aText, id bText, NXCoord *height)
{
    static NXRect myFrame;
    NXRect aFrame, bFrame;
    [[aText sizeToFit] calcLine];
    [aText getFrame:&aFrame];
    [[bText sizeToFit] calcLine];
    [bText getFrame:&bFrame];
    *height = MAX(NX_HEIGHT(&aFrame), NX_HEIGHT(&bFrame));
    NXSetRect(&myFrame, 0.0, 0.0, 2*P_WIDE, *height);
    return [[[[View allocFromZone:[aText zone]] initFrame:&myFrame]
	     addSubview:aText] addSubview:bText];
}    

/*====================================================================
                   Implementation of class FlashDoc                   
====================================================================*/
@implementation FlashDoc : Document
{
    BOOL viewMode;
    BOOL cardChanged;
    id textView[NTextViews];
    id rand;
    id reveal;
    id diff;
    id markCount;
    unsigned int current;
}

/*====================================================================
                         Initialize Methods                          
====================================================================*/

/*--------------------------------------------------------------------
|-getWindow| 
 * set textViews and their delegates; orders the window front;
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-getWindow
{
    static const char * const defaultName[] = {"KeyFont", "NXFont"};
    static NXRect SplitRect;
    int kr;
 
    [self getWindowFromNib:"Card.nib"];

    rand = [Random new];
    current = [contents count];
    viewMode = YES;
    if (!current) [self switchViewMode:self];

    [view setDelegate:self];
    [view getBounds:&SplitRect];
    NX_HEIGHT(&SplitRect) /= 3;
    for(kr=0; kr < NTextViews; kr++) {
	id scroll = getScroll(&SplitRect, &textView[kr], defaultName[kr]);
	[view addSubview:scroll];
	[textView[kr] setDelegate:self];
    }
    [[view adjustSubviews] update];
    view = nil;
    [self nextCard];
    return self;
}

/*====================================================================
                             Provide Card                             
====================================================================*/
/*--------------------------------------------------------------------
|-nextCard| 
Go to next card.  If at end, wrap or generate a new blank card.
Does NOT handle synchronization.
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-nextCard
{
    cardChanged = NO;
    if (!current) {
	if (!viewMode)
	    [self addCard];
	else
	    Notify("Deck", "End of Deck.  Starting over.");
	current = [contents count];
    }
    --current;
    return [self showCard];
}

/*--------------------------------------------------------------------
|-addCard| 
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-addCard
{
    id card = [[ZAlloc(Card) initCount:2] setIntValue:NX_CENTERED at:KEY];
    int i = NTextViews;
    while (i--)
    	[card setStringValue:" " at:i];
    cardChanged = YES;
    [contents addObject:card];
    Notify("Deck", "New Card added at beginning of Deck.");
    return self;
}

/*--------------------------------------------------------------------
|-(BOOL)sync| 
Make sure what is on the screen is the same as what is on the card.
Sync as needed.
YES if not changed or synchronized, NO if aborted.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-(BOOL)sync
{
    if (cardChanged) {
	switch(NXRunAlertPanel("Edit", "Card modified.  Record changes?",
			       "OK", "Discard Changes", NULL)) {
	  case NX_ALERTALTERNATE: 
	    return NO;
	  default:
	    [self saveCard];
	    return YES;
	}
    }
    return YES;
}


/*====================================================================
                       Card Display Methods                        
====================================================================*/

/*--------------------------------------------------------------------
|-showCard| 
 * Display card.  Blacken RES if in View Mode
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-showCard
{
	if ([reveal state] && viewMode) [reveal performClick:self];
	readFromCard([contents objectAt:current], textView);
	[markCount setIntValue:current];
	[window makeFirstResponder:window];
	[[window contentView] update];
	return self;
}

/*--------------------------------------------------------------------
|-saveCard| 
Replace current with the contents of the view.
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-saveCard
{
    writeToCard([contents objectAt:current],textView);
    cardChanged = NO;
    [window setDocEdited:YES];
   return self;
}

/*====================================================================
                           State Variables                            
====================================================================*/

/*--------------------------------------------------------------------
|-view| 
 * Returns the TextView which is first responder;
 * Returns nil if firstResponder is not Text.
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-view
{
	view = [window firstResponder];
	if (!ClassKind(view, [Text class]))
		view = nil;
	return view;
}

/*--------------------------------------------------------------------
|-(BOOL) viewMode;| 
 * Returns the viewMode associated with this document.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-(BOOL) viewMode;
{
    return viewMode;
}

/*--------------------------------------------------------------------
|-setViewMode:(BOOL)newMode| 
 * Change between viewing and editing mode
Returns nil if can't change, otherwise self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-setViewMode:(BOOL)newMode
{
    viewMode = newMode;
    [self sync];
    if (viewMode) {
	[self cover:self];
	[[reveal setTitle:"Reveal"] setAction:@selector(reveal:)];
	[[[reveal  setAltTitle:"Cover"] setType:NX_TOGGLE] setState:0];
	SetDiffCell(0,Easy);
	SetDiffCell(1,Fair);
	SetDiffCell(2,Hard);
    } else {
	[self reveal:self];
	[[reveal setTitle:"Next"] setAction:@selector(next:)];
	[reveal setType:NX_MOMENTARYPUSH];
	SetCell(0,Revert,revertCard:);
	SetCell(1,Modify,modify:);
	SetCell(2,Delete,delete:);
	//	[[window makeFirstResponder:textView[KEY]] makeKeyWindow];
    }
    return self;
}

/*--------------------------------------------------------------------
|-switchViewMode:sender| 
 * Toggle between view and edit mode
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-switchViewMode:sender
{
    return [self setViewMode:(! viewMode)];
}

/*====================================================================
                            Action Methods                            
====================================================================*/

/* Action methods for Card View/Edit */

/*--------------------------------------------------------------------
|-reveal:sender| 
 * If I ask, display RES.  If Button asks, toggle.
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-reveal:sender
{
    id scroll = [[textView[RES] superview] superview];
    if ((sender == self) || ([scroll backgroundGray] == NX_BLACK))
	[[scroll setBackgroundGray:NX_WHITE] update];
    else
	[self cover:self];
    return self;
}

/*--------------------------------------------------------------------
|-cover:sender| 
 * Blacken RES.
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-cover:sender
{
    id scroll = [[textView[RES] superview] superview];
    [[scroll setBackgroundGray:NX_BLACK] update];
    return self;
}

/*--------------------------------------------------------------------
|-wasEasy:sender| 
 * Place in Back.
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-wasEasy:sender
{
    [self nextCard];
    return self;
}

/*--------------------------------------------------------------------
|-wasFair:sender| 
Make sure you will see it again.
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-wasFair:sender
{
    int location = [rand randMin:0 max:current];
    id card = [contents removeObjectAt:current];
    [contents insertObject:card at:location];
    ++current;
    [self nextCard];
    return self;
}
	
/*--------------------------------------------------------------------
|-wasHard:sender| 
Make sure you will see it again soon.
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-wasHard:sender
{
    int location = [rand randMin:(current/2) max:current];
    id card = [contents removeObjectAt:current];
    [contents insertObject:card at:location];
    ++current;
    [self nextCard];
    return self;
}

/*====================================================================
                              Edit Mode                             
====================================================================*/

/*--------------------------------------------------------------------
|-next:sender| 
 * Show the next card.  If current one has been changed, ask.
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-next:sender
{
    [self sync];
    [self nextCard];
    return self;
}

/*--------------------------------------------------------------------
|-revertCard:sender| 
Undo whatever changes have been made to this Card
Returns self.
                                          Ernest Prabhakar/1991-Oct-07
--------------------------------------------------------------------*/
-revertCard:sender
{
    readFromCard([contents objectAt:current],textView);    
    return self;
}

/*--------------------------------------------------------------------
|-modify:sender| 
 * Change the current Card.
Returns self.
                                          Ernest Prabhakar/1991-Oct-12
--------------------------------------------------------------------*/
-modify:sender
{
    [self saveCard];
    return [self nextCard];
}

/*--------------------------------------------------------------------
|-delete:sender|  * Delete the current Card.
Returns self.
					  Ernest Prabhakar/1991-Oct-12
--------------------------------------------------------------------*/
-delete:sender
{
    if (NXRunAlertPanel("Delete", "Are you sure you want to delete this card?", "YES", "NO", "Cancel") != NX_ALERTDEFAULT)
        return self;
    [window setDocEdited:YES];
    [[contents removeObjectAt:current] free];
    return [self nextCard];
}

/*====================================================================
              Action Methods for manipulating the Deck
====================================================================*/

/*--------------------------------------------------------------------
|-previous:sender| 
 * Move back one.
Returns self.
                                          Ernest Prabhakar/1991-Oct-12
--------------------------------------------------------------------*/
-previous:sender
{
    [self sync];
    current += 2;
    if (current > [contents count]) current = [contents count];
    return [self nextCard];
}

/*--------------------------------------------------------------------
|-find:sender|  * Find a string.  Use a panel to set parameters
Returns self.
                                          Ernest Prabhakar/1991-Oct-14
--------------------------------------------------------------------*/
-find:sender
{
    id card = [super find:sender];
    if (card) {
	[self sync];
	current = [contents indexOf:card] + 1;
	[self nextCard];
    }
    return self;
}

/*--------------------------------------------------------------------
|-doAct:(SEL)action for:(STR)caller| 
  * Pass action methods on to Deck
Returns self.
                                          Ernest Prabhakar/1991-Oct-12
--------------------------------------------------------------------*/
-doAct:(SEL)action for:(STR)caller
{
    [self sync];
    [contents perform:action];
    [window setDocEdited:YES];
    current = [contents count];
    return [self nextCard];
}
  
/*--------------------------------------------------------------------
|-shuffle:sender| 
 * Shuffle the deck.
Returns self.
                                          Ernest Prabhakar/1991-Oct-12
--------------------------------------------------------------------*/
-shuffle:sender
{
    return [self doAct:@selector(shuffle) for:"shuffle"];
}

/*--------------------------------------------------------------------
|-sort:sender| 
 * Organize in some coherent fashion.  Use a default sort on key.
Returns self.
                                          Ernest Prabhakar/1991-Oct-12
--------------------------------------------------------------------*/
-sort:sender
{
    [self sync];
    [contents sort];
    return [self doAct:@selector(reverse) for:"sort"];
}

-swapFields:sender
/*
 * Exchange Key and Response.
 */
{
#ifdef SWAPPING
    return [self doAct:@selector(rotate) for:"swap fields"];
#else
    return nil;
#endif
}

- reset:sender
{
    [self sync];
    current = [contents count];
    [markCount setIntValue:current];
    return [self nextCard];
}

/*====================================================================
                   Action Methods for File services                   
====================================================================*/
	
/*--------------------------------------------------------------------
|-print:sender| 
 * Create a view displaying the entire deck, staggered.  Then print it.
Returns self. *** NOT FULLY FUNCTIONAL ***
                                          Ernest Prabhakar/1991-Oct-15
--------------------------------------------------------------------*/
-print:sender
{
	NXRect myRect;
	Window *holder;
	id deckView;
	id aText, bText;
	NXCoord Height, height;
	int count;

	[self sync];
	count = [contents count];
	NXSetRect(&myRect, 0.0, 0.0, 2*P_WIDE, (count+1)*P_HIGH);
	holder = [ZAlloc(Window) initContent:&myRect style:NX_PLAINSTYLE
		  backing: NX_NONRETAINED buttonMask:0 defer:NO];
	deckView = [ZAlloc(NXSplitView) initFrame:&myRect];
[holder setContentView:deckView];
	Height = count * [deckView dividerHeight];

				/* Set up Prototype Text and Box */
	bText = newText(1);
	[bText setText:[self filename]];
	while (count--) {
	    id card = [contents objectAt:count];
	    aText = newText(0);
	    setText(aText, [card stringStreamAt:0], [card intValueAt:0]);
	    [deckView addSubview:wrapViews(aText, bText, &height)];
	    Height += height;
	    bText = newText(1);
	    setText(bText, [card stringStreamAt:1], [card intValueAt:1]);
	}
	aText = newText(0);
	[aText setText:[self filename]];
	[deckView addSubview:wrapViews(aText, bText, &height)];
	Height += height;
	NXSetRect(&myRect, 0.0, 0.0, 2*P_WIDE, Height);	
	[[deckView setFrame:&myRect] printPSCode:sender];
	[holder close];
	return self;
}

/*====================================================================
                        Text delegate methods.                        
====================================================================*/

/*--------------------------------------------------------------------
|-(BOOL)textWillChange:sender|  Doesn't allow editing in view mode.
Returns self.
                                          Ernest Prabhakar/1991-Oct-14
--------------------------------------------------------------------*/
-(BOOL)textWillChange:sender
{
    if (viewMode) {
	Notify("Edit", "Can only edit cards in Edit Mode.");
	return YES;
    } else {
	cardChanged = YES;
	return NO;
    }
}

@end
