#import "WebServerController.h"
#import "AppDelegate.h"
#import "Tune.h"
#import "PlaylistViewController.h"
#import "TuneView.h"//FIXME
#import "NSStringURIQuery.h"
#import "HTTPTuneResponse.h"

#define HTTP_SERVER_4 1

@implementation WebServerController

-(void)viewDidLoad {
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
  return YES;
}

- (void)didReceiveMemoryWarning
{
	[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview.
	// Release anything that's not essential, such as cached data.
}

-(void)dealloc
{
	[httpServer release];
	[super dealloc];
}

- (IBAction) onStart:(id)sender
{
}

- (IBAction) onStop:(id)sender
{
}

#pragma mark UIViewController delegate methods

- (void)viewDidAppear:(BOOL)animated
{
  [self startServer];
}

- (void)viewDidDisappear:(BOOL)animated
{
  [self stopServer];
}

- (IBAction)stopWebServer:(id)sender
{
  [self dismissModalViewControllerAnimated:YES];
}

- (void)startedReceiving:(id)sender
{
  if ( activityIndicator.hidden ) {
    [activityIndicator startAnimating];
    activityIndicator.hidden = NO;
    activityLabel.text = @"Receiving file";
  }
}

- (void)stoppedReceiving:(id)sender
{
  [activityIndicator stopAnimating];
  activityIndicator.hidden = YES;
  activityLabel.text = @"";
}

- (void) startServer
{
  [activityIndicator startAnimating];
  activityIndicator.hidden = NO;
  activityLabel.text = @"Starting server";

	httpServer = [HTTPServer new];
	[httpServer setType:@"_http._tcp."];
  NSString *hostName = [[NSProcessInfo processInfo] hostName];
  [httpServer setName:[NSString stringWithFormat:@"Tunebook - %@", hostName, [httpServer port]]];
	[httpServer setConnectionClass:[TunebookHTTPConnection class]];
	[httpServer setDocumentRoot:[NSURL fileURLWithPath:@"/"]];

  NSError *startError = nil;
  if (![httpServer start:&startError] ) {
//    NSLog(@"Error starting server: %@", startError);
    NSString *message = [NSString stringWithFormat:@"Failed to start the Web server: %@", [startError localizedDescription]];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:message delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alert show];
    [alert release];
  } else {
//    NSLog(@"Starting server on port %d", [httpServer port]);
    NSString *hostName = [[NSProcessInfo processInfo] hostName];
    urlLabel.text = [NSString stringWithFormat:@"http://%@:%d", hostName, [httpServer port]];
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startedReceiving:) name:@"connectionCreated" object:nil];
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(stoppedReceiving:) name:@"connectionDied" object:nil];
  }

  [activityIndicator stopAnimating];
  activityIndicator.hidden = YES;
  activityLabel.text = @"";
}

- (void) stopServer
{
  isServing = NO;
  [httpServer stop];
  [httpServer release];
  httpServer = nil;
}

@end


@implementation TunebookHTTPConnection

- (void)dealloc
{
  if ( parser )
    [parser dealloc];
	[super dealloc];
}

- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)relativePath
{
	if ([@"POST" isEqualToString:method])
	{
    numTunes = 0;
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    [appDelegate startTransaction];
    parser = [[TuneParser alloc] init];
    parser.delegate = self;
    [parser retain];

		return YES;
	}
	
	return [super supportsMethod:method atPath:relativePath];
}


/**
 * Returns whether or not the server will accept POSTs.
 * That is, whether the server will accept uploaded data for the given URI.
**/
- (BOOL)supportsPOST:(NSString *)path withSize:(UInt64)contentLength
{
//	NSLog(@"POST:%@", path);
	
	dataStartIndex = 0;
//	multipartData = [[NSMutableArray alloc] init];
	postHeaderOK = FALSE;
	
	return YES;
}


/**
 * This method is called to get a response for a request.
 * You may return any object that adopts the HTTPResponse protocol.
 * The HTTPServer comes with two such classes: HTTPFileResponse and HTTPDataResponse.
 * HTTPFileResponse is a wrapper for an NSFileHandle object, and is the preferred way to send a file response.
 * HTTPDataResopnse is a wrapper for an NSData object, and may be used to send a custom response.
**/
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
{
  AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
//	NSData *requestData = [(NSData *)CFHTTPMessageCopySerializedMessage(request) autorelease];
//  NSString *requestStr = [[[NSString alloc] initWithData:requestData encoding:NSASCIIStringEncoding] autorelease];
	
	if (requestContentLength > 0)  // Process POST data
	{
//		if ([multipartData count] < 2) return nil;
//		
//		NSString* postInfo = [[NSString alloc] initWithBytes:[[multipartData objectAtIndex:1] bytes]
//													  length:[[multipartData objectAtIndex:1] length]
//													encoding:NSUTF8StringEncoding];
//		
//		NSArray* postInfoComponents = [postInfo componentsSeparatedByString:@"; filename="];
//		postInfoComponents = [[postInfoComponents lastObject] componentsSeparatedByString:@"\""];
//		postInfoComponents = [[postInfoComponents objectAtIndex:1] componentsSeparatedByString:@"\\"];
//		NSString* filename = [postInfoComponents lastObject];
//		
//		if (![filename isEqualToString:@""]) //this makes sure we did not submitted upload form without selecting file
		{
			
//		[postInfo release];
//      [multipartData release];
      requestContentLength = 0;
      
      [[NSNotificationCenter defaultCenter] postNotificationName:@"connectionDied" object:self]; 
      [parser release];
      parser = nil;
      [appDelegate commitTransaction];

      if ( numTunes ) {
        NSString *message = [NSString stringWithFormat:@"Imported %d tunes into the tunebook.", numTunes];
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Imported Tunes" message:message delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];
        [alert release];

        [[NSNotificationCenter defaultCenter] postNotificationName:@"databaseChanged" object:self]; 

        static NSString *importedPage;
        if ( !importedPage ){
          NSString *file = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"serverOK.html"];
          importedPage = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
          [importedPage retain];
        }
        message = [NSString stringWithFormat:importedPage, numTunes];
        return [[[HTTPDataResponse alloc] initWithData:[message dataUsingEncoding:NSUTF8StringEncoding]] autorelease];
      }
    }
  }
	
  if ([path isEqualToString:@"/"]) {
    NSString *indexFile = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"serverindex.html"];
		return [[[HTTPFileResponse alloc] initWithFilePath:indexFile] autorelease];
  }
	
  // Serve the index of tunes
  if ([path hasPrefix:@"/tunes?"]) {
    int maxResults = 100;
    int start = 0;
    NSDictionary *params = [[path substringFromIndex:7] queryDictionaryUsingEncoding:NSUTF8StringEncoding];
    NSString *bookString = [params objectForKey:@"tunebook"];
    NSString *startString = [params objectForKey:@"start"];
    NSString *maxString = [params objectForKey:@"max"];
    if ( startString )
      start = [startString intValue];
    if ( start < 0 )
      start = 0;
    if ( maxString )
      maxResults = [maxString intValue];
    if ( maxResults < 1 )
      maxResults = 1;

    NSArray* tunes = nil;
    if ( bookString != nil ) {
      int book = [bookString intValue];
      if ( book >= 0 && book < [appDelegate.playlists count] ) {
        Playlist *playlist = [appDelegate.playlists objectAtIndex:book];
        tunes = [playlist loadTunes];
      }
    }
    if ( tunes == nil )
      tunes = appDelegate.tunes;
    int count = [tunes count];
    int end = start+maxResults;
    if ( end > count )
      end = count;
    NSMutableString *s = [[NSMutableString alloc] init];
    [s appendString:@"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />"];
    [s appendString:@"<style type='text/css'>\nbody {	font-family: Helvetica, Verdana, Arial, Sans-Serif; }\n.title { font-size: 24pt; margin: 0; }\n"];
    [s appendString:@".tunelinks { float:right; }\n"];
    [s appendString:@".page { width:768px; }\n"];
    [s appendString:@"li { font-size:16; height:32px; line-height: 32px; font-weight: bold; border-bottom: 1px solid #E0E0E0; }\n"];
    [s appendString:@"ul { list-style:none; }\n"];
    [s appendString:@"</style>"];
    [s appendString:@"</head><body><div class='page'>\n"];
    [s appendFormat:@"<div class='title'><img src='banner.jpg'></div><p><b>Tunes %d to %d of %d</b></p>\n", start+1, end, count];
    [s appendString:@"<ul>\n"];

    if ( bookString )
      [s appendFormat:@"<p><a href='/tunebook?id=%d'>All tunes in ABC</a>\n", [bookString intValue]];
    else
      [s appendFormat:@"<p><a href='/tunebook?'>All tunes in ABC</a>\n"];

    for ( int i = start; i < end; i++ ) {
      Tune *tune = [tunes objectAtIndex:i];
      int key = tune.primaryKey;
      [s appendFormat:@"<li>%@ <span class='tunelinks'><a href='/tune?id=%d'>ABC</a> &bull; <a href='/tune?id=%d&format=pdf'>PDF</a> &bull; <a href='/tune?id=%d&format=midi'>MIDI</a></span></li>\n", tune.title, key, key, key];//FIXME-HTML escapes
    }

    if ( start > 0 )
      [s appendFormat:@"<br/><a href='/tunes?start=%d&max=%d'>Previous</a>\n", start-maxResults, maxResults];
    if ( end < count )
      [s appendFormat:@"<br/><a href='/tunes?start=%d&max=%d'>Next</a>\n", start+maxResults, maxResults];

    [s appendString:@"</ul></div></body></html>\n"];
    [s autorelease];
    return [[[HTTPDataResponse alloc] initWithData:[s dataUsingEncoding:NSUTF8StringEncoding]] autorelease];
  }

  // Serve an individual tune
  if ([path hasPrefix:@"/tune?"]) {
    NSDictionary *params = [[path substringFromIndex:6] queryDictionaryUsingEncoding:NSUTF8StringEncoding];
    NSString *idString = [params objectForKey:@"id"];
    NSString *formatString = [params objectForKey:@"format"];
    if ( idString ) {
      int key = [idString intValue];
      Tune *tune = [appDelegate tuneWithPrimaryKey:key];
      if ( tune ) {
        [tune hydrate];
        HTTPDataResponse *response;
        if ( formatString && [formatString isEqualToString:@"pdf"]) {
          response = [[[HTTPDataResponse alloc] initWithData:[Tune pdfDataForTune:tune.dots]] autorelease];
#if HTTP_SERVER_4
          [response.httpHeaders setValue:@"application/pdf" forKey:@"Content-Type"]; 
#else
          [response setContentType:@"application/pdf"];
#endif
        } else if ( formatString && [formatString isEqualToString:@"midi"]) {
          response = [[[HTTPDataResponse alloc] initWithData:[Tune midiDataForTune:tune.dots]] autorelease];
#if HTTP_SERVER_4
          [response.httpHeaders setValue:@"audio/midi" forKey:@"Content-Type"]; 
#else
          [response setContentType:@"audio/midi"];
#endif
        } else {
          response = [[[HTTPDataResponse alloc] initWithData:[tune.dots dataUsingEncoding:NSUTF8StringEncoding]] autorelease];
#if HTTP_SERVER_4
          [response.httpHeaders setValue:@"text/plain" forKey:@"Content-Type"]; 
#else
          [response setContentType:@"text/plain"];
#endif
        }
        [tune release];
        return response;
      }
    }
  }

  // Serve the list of tunebooks
  if ([path hasPrefix:@"/tunebooks"]) {
    NSMutableString *s = [[NSMutableString alloc] init];
    [s appendString:@"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />"];
    [s appendString:@"<style type='text/css'>\nbody {	font-family: Helvetica, Verdana, Arial, Sans-Serif; }\n.title { font-size: 24pt; margin: 0; }\n"];
    [s appendString:@".tunelinks { float:right; }\n"];
    [s appendString:@".page { width:768px; }\n"];
    [s appendString:@"li { font-size:16; height:32px; line-height: 32px; font-weight: bold; border-bottom: 1px solid #E0E0E0; }\n"];
    [s appendString:@"ul { list-style:none; }\n"];
    [s appendString:@"</style>"];
    [s appendString:@"</head><body><div class='page'>\n"];
    [s appendFormat:@"<div class='title'><img src='banner.jpg'></div><p><b>Tunebooks</b></p>\n"];
    [s appendString:@"<ul>\n"];

    int count = [appDelegate.playlists count];
    for ( int i = 0; i < count; i++ ) {
      Playlist *playlist = [appDelegate.playlists objectAtIndex:i];
      [s appendFormat:@"<li><a href='/tunes?tunebook=%d'>%@</a></li>\n", i, playlist.name];//FIXME-HTML escapes
    }

    [s appendString:@"</ul></div></body></html>\n"];
    [s autorelease];
    return [[[HTTPDataResponse alloc] initWithData:[s dataUsingEncoding:NSUTF8StringEncoding]] autorelease];
  }

  // Serve an individual tunebook
  if ([path hasPrefix:@"/tunebook?"]) {
    NSDictionary *params = [[path substringFromIndex:10] queryDictionaryUsingEncoding:NSUTF8StringEncoding];
    NSString *idString = [params objectForKey:@"id"];
    int book = idString ? [idString intValue] : -1;

    NSArray* tunes = nil;
    if ( book >= 0 && book < [appDelegate.playlists count] ) {
      Playlist *playlist = [appDelegate.playlists objectAtIndex:book];
      tunes = [playlist loadTunes];
      [tunes autorelease];
    }
    if ( tunes == nil )
      tunes = appDelegate.tunes;

    HTTPTuneResponse *response;
    response = [[[HTTPTuneResponse alloc] initWithTunes:tunes] autorelease];
#if HTTP_SERVER_4
    [response.httpHeaders setValue:@"text/plain" forKey:@"Content-Type"]; 
#else
    [response setContentType:@"text/plain"];
#endif
    return response;
  }

  // We are paranoid and only allow specific files to be downloaded
  if ([path isEqualToString:@"/tunebook.css"]) {
    NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"tunebook.css"];
    return [[[HTTPFileResponse alloc] initWithFilePath:path] autorelease];
  }

  if ([path isEqualToString:@"/banner.jpg"]) {
    NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"banner.jpg"];
    return [[[HTTPFileResponse alloc] initWithFilePath:path] autorelease];
  }

  if ([path isEqualToString:@"/tunedb.sql"]) {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"tunedb.sql"];
    HTTPFileResponse *response = [[[HTTPFileResponse alloc] initWithFilePath:path] autorelease];
#if HTTP_SERVER_4
    [response.httpHeaders setValue:@"filename=tunedb.sql" forKey:@"Content-Disposition"]; 
#endif
    return response;
  }

	return nil;
}

/**
 * This method is called to handle data read from a POST.
 * The given data is part of the POST body.
**/
- (void)processDataChunk:(NSData *)postDataChunk
{
	// Override me to do something useful with a POST.
	// If the post is small, such as a simple form, you may want to simply append the data to the request.
	// If the post is big, such as a file upload, you may want to store the file to disk.
	// 
	// Remember: In order to support LARGE POST uploads, the data is read in chunks.
	// This prevents a 50 MB upload from being stored in RAM.
	// The size of the chunks are limited by the POST_CHUNKSIZE definition.
	// Therefore, this method may be called multiple times for the same POST request.
	
	//NSLog(@"processPostDataChunk");
	
  [[NSNotificationCenter defaultCenter] postNotificationName:@"connectionCreated" object:self]; 

	if (!postHeaderOK) {
		UInt16 separatorBytes = 0x0A0D;
		NSData *separatorData = [NSData dataWithBytes:&separatorBytes length:2];
		
		int l = [separatorData length];

		for (int i = 0; i < [postDataChunk length] - l; i++) {
			NSRange searchRange = {i, l};

			if ([[postDataChunk subdataWithRange:searchRange] isEqualToData:separatorData]) {
				NSRange newDataRange = {dataStartIndex, i - dataStartIndex};
				dataStartIndex = i + l;
				i += l - 1;
				NSData *newData = [postDataChunk subdataWithRange:newDataRange];

				if ([newData length]) {
//					[multipartData addObject:newData];
//          [parser parseData: newData];
				} else {
					postHeaderOK = TRUE;

//					NSString* postInfo = [[NSString alloc] initWithBytes:[[multipartData objectAtIndex:1] bytes] length:[[multipartData objectAtIndex:1] length] encoding:NSUTF8StringEncoding];
//					NSArray* postInfoComponents = [postInfo componentsSeparatedByString:@"; filename="];
//					postInfoComponents = [[postInfoComponents lastObject] componentsSeparatedByString:@"\""];
//					postInfoComponents = [[postInfoComponents objectAtIndex:1] componentsSeparatedByString:@"\\"];
//					NSString* filename = [[[server documentRoot] path] stringByAppendingPathComponent:[postInfoComponents lastObject]];

          // Strip the last separator off the end
          NSInteger end = [postDataChunk length];
//          for (int i = dataStartIndex; i < end - l; i++) {
//            NSRange searchRange = {i, l};
//
//            if ([[postDataChunk subdataWithRange:searchRange] isEqualToData:separatorData]) {
//              end = i;
//              break;
//            }
//          }

					NSRange fileDataRange = {dataStartIndex, end - dataStartIndex};
          NSData *fileData = [postDataChunk subdataWithRange:fileDataRange];

          [parser parseData: fileData];
//					[postInfo release];
					
					break;
				}
			}
		}
	} else {
    [parser parseData: postDataChunk];
	}
}

- (void) parsedTune:(TuneParser *)parser tune:(Tune *)tune
{
  AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
  [appDelegate addTune:tune];
  numTunes++;
}

- (NSData *)preprocessErrorResponse:(CFHTTPMessageRef)response;
{
  if(CFHTTPMessageGetResponseStatusCode(response) == 404)
  {
    NSString *indexFile = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"404.html"];
    NSString *msg = [NSString stringWithContentsOfFile:indexFile encoding:NSUTF8StringEncoding error:nil];
    NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];

    CFHTTPMessageSetBody(response, (CFDataRef)msgData);

    NSString *contentLengthStr = [NSString stringWithFormat:@"%u", (unsigned)[msgData length]];
    CFHTTPMessageSetHeaderFieldValue(response, CFSTR("Content-Length"), (CFStringRef)contentLengthStr);
  }
  return [super preprocessErrorResponse:response];
}

@end
