/*$Copyright:
 * Copyright (C) 1992.5.22. Recruit Co.,Ltd. 
 * Institute for Supercomputing Research
 * All rights reserved.
 * NewsBase  by ISR, Kazuto MIYAI, Gary ARAKAKI, Katsunori SUZUKI, Kok-meng Lue
 *
 * You may freely copy, distribute and reuse the code in this program under 
 * following conditions.
 * - to include this notice in the source code, if it is to be distributed 
 *   with source code.
 * - to add the file named "COPYING" within the code, which shall include 
 *   GNU GENERAL PUBLIC LICENSE(*).
 * - to display an acknowledgement in binary code as follows: "This product
 *   includes software developed by Recruit Co.,Ltd., ISR."
 * - to display a notice which shall state that the users may freely copy,
 *   distribute and reuse the code in this program under GNU GENERAL PUBLIC
 *   LICENSE(*)
 * - to indicate the way to access the copy of GNU GENERAL PUBLIC LICENSE(*)
 *
 *   (*)GNU GENERAL PUBLIC LICENSE is stored in the file named COPYING
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
$*/

#import <appkit/appkit.h>
#import <mach/mach_init.h>
#import <string.h>
#import <defaults/defaults.h>
#import <dpsclient/event.h>
#import <libc.h>

#import "IItemHeaderBrowser.h"
#import "INXBrowserCellWithLinkedObject.h"
#import "IUifNode.h"
#import "IOrderedListD.h"
#import "InfoD.h"
#import "INewsGroupTreeControl.h"
#import "INewsgroupInfoD.h"
#import "ITreeNodeD.h"
#import "ITreeBrowserMatrix.h"
#import "errdebug.h"
#import "data_types.h"
#import "Localization.h"

#define		MAX_NO_OF_TOKENS	256
#define 	MAXVISIBLECOL 	2
#define 	MINCOLWIDTH	160

extern id currentNewsGroup;

@implementation IItemHeaderBrowser

- initFrame:(NXRect *)frameRect
{
    [super initFrame:frameRect];
    [self setMinColumnWidth:MINCOLWIDTH];
    [self setMaxVisibleColumns:MAXVISIBLECOL];
    [self setAction:@selector(singleClickLeafPreHandler:)];
    [self setDoubleAction:@selector(doubleClickLeafPreHandler:)];

    [self acceptArrowKeys:YES andSendActionMessages:NO];
    return self;
}

- (BOOL)acceptsFirstResponder
{
    return YES;
}

- loadColumnZero
{
    // make self first responder when loading column and column has
    // any cells. if self(browser) has no column, then refuse to get 
    // first responder. data group browser might get first responder.
    [super loadColumnZero];
    if ([[[self matrixInColumn:0] cellList] count] > 0) {
	[window makeFirstResponder:self];
    }
    return self;
}

/*
    The target object and action message of itemheader is IItemHeaderBrowser
    and articleHandler.  I.e., articleHandler is called when a article is
    selected.
*/
- (void)singleClickLeafPreHandler:sender
{
    ITreeBrowserMatrix	*lastColumnMatrix;
    int		keyFlag;

    lastColumnMatrix = [self matrixInColumn:[self lastColumn]];
    if (((keyFlag=(int)[lastColumnMatrix flagsOfLastEvent]) & NX_COMMANDMASK)
					    && (keyFlag & NX_CONTROLMASK)) {
	// 'command' + 'control' click
	// post cancel article
	if (NXRunAlertPanel(LoStr("NewsBase"),
		LoStr("Are you sure to cancel the article?"),
		LoStr("NO"),LoStr("YES"),NULL) == 0) {
	    [lastColumnMatrix resetFlagsOfLastEvent];
	    [self cancelArticle];
	    return;
	} else {
	    /* Cancel is clicked */
	    return;
	}
    }

    if (strcmp(NXGetDefaultValue(OWNER, ARTICLESELECT),SINGLECLICK)==0) {
	[self leafPreHandler:sender];
	return;
    } else {
//        lastColumnMatrix = [self matrixInColumn:[self lastColumn]];
	if (keyFlag & NX_CONTROLMASK) {
	    DBG(1,fprintf(stderr,"-- IItemHeaderBrowser:singleClick"));
	    // don't have to reset flags in matrix because "leafPreHander:"
	    // will do it in next statement.
	    [self leafPreHandler:sender];
	    return;
	}
	return;
    }
}

- (void)doubleClickLeafPreHandler:sender
{
    if (strcmp(NXGetDefaultValue(OWNER, ARTICLESELECT),DOUBLECLICK)==0) {
	[self leafPreHandler:sender];
	return;
    } else {
	return;
    }
}

- (void)leafHandler:sender
{
    id 		selectedCell;
    id		articleItem;
    id		node;

    [window makeFirstResponder:self];	// set first responder to self
    selectedCell = [[self matrixInColumn:[self lastColumn]] selectedCell];

    if ([selectedCell isLeaf]) {
        node = [selectedCell node];
        if ((articleItem = [iIOmodule itemOf:node])==nil) {
	    return;
	}
	
	/* set no active to cell */
	if (([node active]==YES) && ([iIOmodule toggleActive:node]==YES)) {
            [selectedCell setActive:NO];
	    [node setActive:NO];
	}
	[[self matrixInColumn:[self lastColumn]] display];
	return;
    }
    return;
}

- directoryHandler:node
{
    [window makeFirstResponder:self];	// set first responder to self
    return [iIOmodule itemHeadersOf:node];
}

- keyDown:(NXEvent *)theEvent
{
    unsigned int	ch;
    id		selectedCell;
    
    DBG(1,fprintf(stderr,"  keydown"));
    selectedCell = [[self matrixInColumn:[self selectedColumn]] selectedCell];

    if ((ch=(theEvent->data).key.charCode) == NX_CR) {
	// "Return" key pressed
	DBG(1,fprintf(stderr,"  NX_CR"));
	// click selected cell
        if ([selectedCell isLeaf] ==YES) {
	    [self leafHandler:self];
	}
	return self;
    }
    if (ch == 0x20 || ch == 0x80) {
	//  0x20 : space
	//  0x80 : alt+space
	if ([self selectedColumn] == -1) {
	    // no cell is selected so select first one
	    [[self matrixInColumn:0] selectCellAt:0 :0];
	}
	selectedCell = [[self matrixInColumn:[self selectedColumn]]
								selectedCell];
	if ((theEvent->flags & NX_SHIFTMASK) == NO) {
	    // select next cell
	    if (theEvent->flags & NX_ALTERNATEMASK) {
		// alternate key is down, select next cell
		selectedCell = [self selectNextCell:selectedCell];
	    } else {
		// start loop for selecting next *active* *leaf* cell
		do {
		    if ([selectedCell isLeaf] == YES
				&& [[selectedCell node] active] == YES) {
			break;
		    }
		    selectedCell = [self selectNextCell:selectedCell];
		} while (selectedCell != NULL);
	    }
	} else {
	    // select prev cell
	    DBG(1,fprintf(stderr,"--- space+shift\n"));
	    [self selectPrevCell:selectedCell];
	}
	if (selectedCell != NULL) {
	    if ((theEvent->flags & NX_ALTERNATEMASK)==NO
			 && (theEvent->flags & NX_SHIFTMASK)==NO) {
		// cell is selected and alt key is not down, click cell
		[self leafHandler:self];
	    }
	    return self;
	} else {
	    // no more cells, so beep
	    return ([super keyDown:theEvent]);
	}
    }
 
    // handle arrow keys in super class
    return ([super keyDown:theEvent]);
}

- selectNextCell:nowselectedCell
{
    return([self _selectCell:nowselectedCell direct:(DirectFlag)FORWARD]);
}

- selectPrevCell:nowselectedCell
{
    return([self _selectCell:nowselectedCell direct:(DirectFlag)BACKWARD]);
}

- _selectCell:nowselectedCell direct:(DirectFlag)dflag
{
    unsigned int	cellIndex;
    id		selectedColumnMatrix;
    id		rightColumnMatrix, leftColumnMatrix;
    int		trow, tcol;		// can not use cellList???
    int		tcellIndex;
    // cellList has old information and sometimes it points cell which
    // is alread deleted
    
    selectedColumnMatrix = [self matrixInColumn:[self selectedColumn]];
    
    if ([nowselectedCell isLeaf] == YES) {
	// no right column
	cellIndex = [[selectedColumnMatrix cellList] indexOf:nowselectedCell];
	[selectedColumnMatrix getNumRows:&trow numCols:&tcol];

	// check if selectedCell is bottom(for FORWARD) or top(for BACKWORD)
	if ((dflag == FORWARD && cellIndex < (trow -1))
			|| (dflag == BACKWARD && cellIndex != 0)) {
	    // select next cell
	    if (dflag == FORWARD) {
		tcellIndex = cellIndex + 1;
	    } else {
		tcellIndex = cellIndex - 1;		
	    }
	    [selectedColumnMatrix selectCellAt:tcellIndex :0];
	    [self scrollToCellAt:tcellIndex matrix:selectedColumnMatrix];
	} else {
	    // this cell is bottom or top
	    if ((leftColumnMatrix=[self matrixInColumn:
	    			([self selectedColumn] -1)]) != nil) {
		// select next cell in left column
		cellIndex = [[leftColumnMatrix cellList]
				indexOf:[leftColumnMatrix selectedCell]];
		[leftColumnMatrix getNumRows:&trow numCols:&tcol];
		if ((dflag == FORWARD && cellIndex < (trow -1))
				|| (dflag == BACKWARD && cellIndex != 0)) {
		    if (dflag == FORWARD) {
			tcellIndex = cellIndex + 1;
		    } else {
			tcellIndex = cellIndex - 1;
		    }
		    [leftColumnMatrix selectCellAt:tcellIndex :0];
		    [self setLastColumn:0];
		    [self scrollToCellAt:tcellIndex matrix:leftColumnMatrix];
		} else {
		    // left cell is last one
		    [self setLastColumn:0];
		    return NULL;
		}
	    } else {
		// no left column
		// this cell is really end
		return NULL;
	    }
	}
    } else {
	// there is right column, but no cell is selected
	// select first cell in right column
	[selectedColumnMatrix lockFocus];
	[selectedColumnMatrix sendAction];
	[selectedColumnMatrix unlockFocus];
	rightColumnMatrix=[self matrixInColumn:([self selectedColumn] +1)];
	if (dflag == FORWARD) {
	    [rightColumnMatrix selectCellAt:0 :0];
	} else {
	    [rightColumnMatrix selectCellAt:
	    				([rightColumnMatrix cellCount]-1) :0];
	}
    }
    
    // return id of selectedCell
    return ([[self matrixInColumn:[self selectedColumn]] selectedCell]);
}

- scrollToCellAt:(int)row matrix:matrix
{
    NXRect	matrixRect, cellRect;
    id		contView;	// clipView
    id		scrollView;	// scollView of a column for NXBrowser
    NXRect	contRect;
    NXRect	scrollerRect;
    NXCoord	cell_y, cont_y;
    
    contView = [matrix superview];
    scrollView = [[matrix superview] superview];
    
    [matrix getFrame:&matrixRect];
    [matrix getCellFrame:&cellRect at:row :0];
    [contView getFrame:&contRect];
    
    cell_y = cellRect.origin.y + cellRect.size.height;
    cont_y = contRect.size.height - cellRect.size.height * 3;
			// 3 cells are always shown after cell in (int)row
    if (cont_y < cell_y) {
	contRect.origin.y = cell_y - cont_y;
    }
    [[scrollView vertScroller] getFrame:&scrollerRect];
    contRect.origin.x = 0.0;
    [contView rawScroll:&(contRect.origin)];
    [scrollView reflectScroll:contView];

    return (self);
}
    
    

- cancelArticle
{
    id		selectedCell;
    id		node;
    id		header;
    const char	*messageID;
    char	*fromValue;
    char	buf[256], *user_name;
    char	*mailAddress;

    selectedCell = [[self matrixInColumn:[self lastColumn]] selectedCell];
    node = [selectedCell node];
    if ((header=[[node linkedData] dataForKey:HEADER_INFO]) == nil) {
	return (nil);
    }
    if ((fromValue=(char *)[header infoForKey:FROM]) == NULL) {
	return (nil);
    }
    // check authentication
    sscanf(fromValue, "%256[^@]", buf);
    DBG(1,fprintf(stderr,"  buf = %s\n", buf));
    if ((user_name=getenv("USER")) == NULL) {
	return (nil);
    }
    if (strcmp(user_name,buf)!=0) {
        NXRunAlertPanel(LoStr("NewsBase"),LoStr("From: field value not matched. Can not cancel"),NULL,NULL,NULL);	
	return (nil);
    } 

    if ((messageID = (const char *)[header infoForKey:MESSAGE_ID]) == NULL) {
	return (nil);
    }
    
    if (NXGetDefaultValue(OWNER,FROM) != NULL) {
	mailAddress = NXCopyStringBuffer(NXGetDefaultValue(OWNER,FROM));
    } else {
	// set default value
	mailAddress = [self getMailAddress];
    }
	
    [iIOmodule cancelArticleMessageID:messageID from:mailAddress];
    free(mailAddress);
    return self;
}

- (char *)getMailAddress
{
    char	*mail_address;
    char	*user_name, machine_name[256], domain_name[257];

    user_name = NXCopyStringBufferFromZone(getenv("USER"),[self zone]);
    gethostname(machine_name,sizeof(machine_name));
    getdomainname(domain_name + 1, sizeof(domain_name) - 1);
    domain_name[sizeof(domain_name) - 1] = '\0';
    if (domain_name[1] != '\0') {
        domain_name[0] = '.';
    } else {
        domain_name[0] = '\0';
    }
    mail_address = (char *)NXZoneMalloc([self zone],
	    strlen(user_name)+strlen(machine_name)+strlen(domain_name)+3);
    (void)sprintf(mail_address,"%s@%s%s",user_name,machine_name,
							    domain_name);
    return mail_address;
}

@end
