/*
 * PrintDrop
 * Copyright (c) 2008, Blue Static <http://www.bluestatic.org>
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU 
 * General Public License as published by the Free Software Foundation; either version 2 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if not, 
 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#import "AppController.h"
#import <libssh2.h>
#import <sys/socket.h>
#import <arpa/inet.h>
#include <netdb.h>

@interface AppController (Private)

- (void)setStatus:(NSString *)msg isError:(BOOL)error;

- (void)readChannel:(LIBSSH2_CHANNEL *)channel;
- (void)uploadAndPrint:(id)sender;

@end


@implementation AppController

/**
 * Set up the printer list
 */
- (id)init
{
	if (self = [super init])
	{
		printers = [NSMutableArray array];
		
		NSArray *values = [NSArray arrayWithObjects:@"Double-Sided, Stapled $0.05", @"publp", nil];
		NSArray *keys = [NSArray arrayWithObjects:@"displayName", @"unixName", nil];
		[printers addObject:[NSDictionary dictionaryWithObjects:values forKeys:keys]];
		
		values = [NSArray arrayWithObjects:@"Single-Sided, Stapled $0.10", @"pubps", nil];
		[printers addObject:[NSDictionary dictionaryWithObjects:values forKeys:keys]];
		
		values = [NSArray arrayWithObjects:@"Double-Sided, Not Stapled $0.05", @"publpns", nil];
		[printers addObject:[NSDictionary dictionaryWithObjects:values forKeys:keys]];
		
		values = [NSArray arrayWithObjects:@"Single-Sided, Not Stapled $0.10", @"pubpsns", nil];
		[printers addObject:[NSDictionary dictionaryWithObjects:values forKeys:keys]];
	}
	return self;
}

/**
 * Sets the status text
 */
- (void)setStatus:(NSString *)msg isError:(BOOL)error
{
	[status setStringValue:msg];
	if (error)
	{
		[status setTextColor:[NSColor redColor]];
		[progress stopAnimation:self];
	}
	else
	{
		[status setTextColor:[NSColor blackColor]];
	}
}

/**
 * Reads through a channel (in non-blocking) mode until there is no more left to read
 * and then it returns. This calls sleep(1) so that the channel can have time to process.
 * Be sure this is threaded otherwis the interface will stall.
 */
- (void)readChannel:(LIBSSH2_CHANNEL *)channel
{
	libssh2_channel_set_blocking(channel, 0);
	
	char buf[1024];
	int numbytes;
	do
	{
		memset(&buf, '\0', sizeof(buf));
		sleep(1);
		numbytes = libssh2_channel_read(channel, buf, sizeof(buf));
	} while (numbytes > 0);
}

/**
 * Sends an item to the printer
 */
- (IBAction)print:(id)sender
{
	[NSThread detachNewThreadSelector:@selector(uploadAndPrint:) toTarget:self withObject:sender];
}

/**
 * Opens an SSH session, creates a SCP channel to upload the file, followed by a shell channel
 * to queue up LPR
 */
- (void)uploadAndPrint:(id)sender
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	[progress startAnimation:self];
	[progress setHidden:NO];
	[status setHidden:NO];
	
	NSString *printer = [[printersController selection] valueForKey:@"unixName"];
	FILE *localFile;
	struct stat fileInfo;
	
	[self setStatus:@"Connecting to acs.bu.edu" isError:NO];
	struct sockaddr_in sin;
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	sin.sin_port = htons(22);
	sin.sin_family = AF_INET;
	
	struct hostent *host = gethostbyname("acs.bu.edu");
	memcpy(&sin.sin_addr, host->h_addr_list[0], host->h_length);
	
	if (stat([[dragRegion filePath] UTF8String], &fileInfo))
	{
		return [self setStatus:@"Invalid file selected" isError:YES];
	}
	
	if (connect(sock, (struct sockaddr *)(&sin), sizeof(struct sockaddr_in)) != 0)
	{
		return [self setStatus:@"Could not connect to acs.bu.edu" isError:YES];
	}
	
	LIBSSH2_SESSION *ssh = libssh2_session_init();
	if (ssh == NULL)
	{
		return [self setStatus:@"Failed to initialize SSH context" isError:YES];
	}
	
	if (libssh2_session_startup(ssh, sock))
	{
		return [self setStatus:@"Could not tunnel over SSH" isError:YES];
	}
	
	if (libssh2_userauth_password(ssh, [[username stringValue] UTF8String], [[password stringValue] UTF8String]))
	{
		[self setStatus:@"Bad username/password" isError:YES];
		goto shutdown;
	}
	
	LIBSSH2_CHANNEL *channel = libssh2_scp_send(ssh, "~/__bu_print_drop__.pdf", 0755, (unsigned long)fileInfo.st_size);
	if (!channel)
	{
		[self setStatus:@"Unable to open upload SCP session" isError:YES];
		goto shutdown;
	}
	
	[self setStatus:@"Uploading file..." isError:NO];
	
	localFile = fopen([[dragRegion filePath] UTF8String], "r");
	char buf[1024];
	char *pbuf;
	int numread, numwrote;
	do
	{
		numread = fread(buf, 1, sizeof(buf), localFile);
		if (numread <= 0)
		{
			break;
		}
		
		pbuf = buf;
		do
		{
			numwrote = libssh2_channel_write(channel, pbuf, numread);
			pbuf += numwrote;
			numread -= numread;
		} while (numwrote > 0);
		
	} while(1);
	
	[self setStatus:@"File uploaded!" isError:NO];
	
	libssh2_channel_send_eof(channel);
	libssh2_channel_wait_eof(channel);
	libssh2_channel_wait_closed(channel);
	libssh2_channel_free(channel);
	channel = NULL;
	
	channel = libssh2_channel_open_session(ssh);
	if (!channel)
	{
		[self setStatus:@"Could not open SSH channel for printing" isError:YES];
		goto shutdown;
	}
	
	if (libssh2_channel_request_pty(channel, "vanilla"))
	{
		[self setStatus:@"Could not open ANSI TTY" isError:YES];
		goto shutdown;
	}
	
	if (libssh2_channel_shell(channel))
	{
		[self setStatus:@"Failed to open remote shell" isError:YES];
		goto shutdown;
	}
	
	[self setStatus:@"Opened remote SSH shell" isError:NO];
	
	// read the banner
	[self readChannel:channel];
	
	// send the job to lpr
	char *cmd = (char *)[[NSString stringWithFormat:@"touch ~/print-%@.drop\r\n\0", printer] UTF8String];
	libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
	[self readChannel:channel];
	
	// remove our temp file
	cmd = "rm -f __bu_print_drop__.pdf\r\n\0";
	libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
	[self readChannel:channel];
	
	[self setStatus:@"Printed!" isError:NO];
	
	libssh2_channel_send_eof(channel);
	libssh2_channel_eof(channel);
	libssh2_channel_close(channel);
	
shutdown:
	if (channel)
	{
		libssh2_channel_free(channel);
		channel = NULL;
	}
	libssh2_session_disconnect(ssh, "Normal disconnect.");
	libssh2_session_free(ssh);
	
	close(sock);
	
	[progress stopAnimation:self];
	
	[pool release];
}

@end
