//
//  MyHTTPConnection.m
//  WebServerIPhone
//
//  CocoaLumberjack Demos
//

#import "MyHTTPConnection.h"
#import "WebServerIPhoneAppDelegate.h"
#import "HTTPLogging.h"
#import "HTTPMessage.h"
#import "HTTPDataResponse.h"
#import "HTTPDynamicFileResponse.h"
#import "GCDAsyncSocket.h"
#import <CocoaLumberjack/CocoaLumberjack.h>
#import "WebSocket.h"
#import "WebSocketLogger.h"

@implementation MyHTTPConnection

static NSMutableSet *webSocketLoggers;

/**
 * The runtime sends initialize to each class in a program exactly one time just before the class,
 * or any class that inherits from it, is sent its first message from within the program. (Thus the
 * method may never be invoked if the class is not used.) The runtime sends the initialize message to
 * classes in a thread-safe manner. Superclasses receive this message before their subclasses.
 *
 * This method may also be called directly (assumably by accident), hence the safety mechanism.
**/
+ (void)initialize
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // We need some place to store the webSocketLogger instances.
        // So we'll store them here, in a class variable.
        // 
        // We'll also use a simple notification system to release them when they die.
        
        webSocketLoggers = [[NSMutableSet alloc] init];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(webSocketLoggerDidDie:)
                                                     name:WebSocketLoggerDidDieNotification
                                                   object:nil];
    });
}

+ (void)addWebSocketLogger:(WebSocketLogger *)webSocketLogger
{
    @synchronized(webSocketLoggers)
    {
        [webSocketLoggers addObject:webSocketLogger];
    }
}

+ (void)webSocketLoggerDidDie:(NSNotification *)notification
{
    @synchronized(webSocketLoggers)
    {
        [webSocketLoggers removeObject:[notification object]];
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Returns the logFileManager, which is a part of the DDFileLogger system.
 * The DDLogFileManager is the subsystem which manages the location and creation of log files.
**/
- (id <DDLogFileManager>)logFileManager
{
    WebServerIPhoneAppDelegate *appDelegate;
    appDelegate = (WebServerIPhoneAppDelegate *)[[UIApplication sharedApplication] delegate];
    
    return appDelegate.fileLogger.logFileManager;
}

/**
 * Dynamic discovery of proper websocket href. 
**/
- (NSString *)wsLocation
{
    NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
    
    NSString *wsLocation;
    NSString *wsHost = [request headerField:@"Host"];
    
    if (wsHost == nil)
    {
        wsLocation = [NSString stringWithFormat:@"ws://localhost:%@/livelog", port];
    }
    else
    {
        wsLocation = [NSString stringWithFormat:@"ws://%@/livelog", wsHost];
    }
    
    return wsLocation;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark /logs.html
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Returns the response body for requests to "/logs/index.html".
 * 
 * The response is generated dynamically.
 * It returns the list of log files currently on the system, along with their creation date and file size.
**/
- (NSData *)generateIndexData
{
    NSArray *sortedLogFileInfos = [[self logFileManager] sortedLogFileInfos];
    
    NSDateFormatter *df = [[NSDateFormatter alloc] init];
    [df setFormatterBehavior:NSDateFormatterBehavior10_4];
    [df setDateFormat:@"yyyy/MM/dd HH:mm:ss"];
    
    NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
    [nf setFormatterBehavior:NSNumberFormatterBehavior10_4];
    [nf setNumberStyle:NSNumberFormatterDecimalStyle];
    [nf setMinimumFractionDigits:2];
    [nf setMaximumFractionDigits:2];
    
    NSMutableString *response = [NSMutableString stringWithCapacity:1000];
    
    [response appendString:@"<html><head>"];
    [response appendString:@"<style type='text/css'>@import url('styles.css');</style>"];
    [response appendString:@"</head><body>"];
    
    [response appendString:@"<h1>Device Log Files</h1>"];
    
    [response appendString:@"<table cellspacing='2'>"];
    
    for (DDLogFileInfo *logFileInfo in sortedLogFileInfos)
    {
        NSString *fileName = logFileInfo.fileName;
        NSString *fileDate = [df stringFromDate:[logFileInfo creationDate]];
        NSString *fileSize;
        
        unsigned long long sizeInBytes = logFileInfo.fileSize;
        
        double GBs = (double)(sizeInBytes) / (double)(1024 * 1024 * 1024);
        double MBs = (double)(sizeInBytes) / (double)(1024 * 1024);
        double KBs = (double)(sizeInBytes) / (double)(1024);
        
        if(GBs >= 1.0)
        {
            NSString *temp = [nf stringFromNumber:[NSNumber numberWithDouble:GBs]];
            fileSize = [NSString stringWithFormat:@"%@ GB", temp];
        }
        else if(MBs >= 1.0)
        {
            NSString *temp = [nf stringFromNumber:[NSNumber numberWithDouble:MBs]];
            fileSize = [NSString stringWithFormat:@"%@ MB", temp];
        }
        else
        {
            NSString *temp = [nf stringFromNumber:[NSNumber numberWithDouble:KBs]];
            fileSize = [NSString stringWithFormat:@"%@ KB", temp];
        }
        
        NSString *fileLink = [NSString stringWithFormat:@"<a href='/logs/%@'>%@</a>", fileName, fileName];
        
        [response appendFormat:@"<tr><td>%@</td><td>%@</td><td align='right'>%@</td>", fileLink, fileDate, fileSize];
    }
    
    [response appendString:@"</table></body></html>"];
    
    return [response dataUsingEncoding:NSUTF8StringEncoding];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark HTTPConnection
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Overrides method in HTTPConnection.
 * 
 * This method is invoked to retrieve the filePath for a given URI.
 * We override it to provide proper mapping for log file paths.
**/
- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory
{
    if ([path hasPrefix:@"/logs/"])
    {
        NSString *logsDir = [[self logFileManager] logsDirectory];
        return [logsDir stringByAppendingPathComponent:[path lastPathComponent]];
    }
    
    // Fall through
    return [super filePathForURI:path allowDirectory:allowDirectory];
}

/**
 * Overrides method in HTTPConnection.
**/
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
{
    if ([path isEqualToString:@"/logs.html"])
    {
        // Dynamically generate html response with list of available log files
        
        NSData *indexData = [self generateIndexData];
        return [[HTTPDataResponse alloc] initWithData:indexData];
    }
    else if ([path isEqualToString:@"/socket.html"])
    {
        // The socket.html file contains a URL template that needs to be completed:
        // 
        // ws = new WebSocket("%%WEBSOCKET_URL%%");
        // 
        // We need to replace "%%WEBSOCKET_URL%%" with whatever URL the server is running on.
        // We can accomplish this easily with the HTTPDynamicFileResponse class,
        // which takes a dictionary of replacement key-value pairs,
        // and performs replacements on the fly as it uploads the file.
        
        NSString *loc = [self wsLocation];
        NSDictionary *replacementDict = [NSDictionary dictionaryWithObject:loc forKey:@"WEBSOCKET_URL"];
        
        return [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
                                                    forConnection:self
                                                        separator:@"%%"
                                            replacementDictionary:replacementDict];
    }
    
    // Fall through
    return [super httpResponseForMethod:method URI:path];
}

/**
 * Overrides method in HTTPConnection.
**/
- (WebSocket *)webSocketForURI:(NSString *)path
{
    if ([path isEqualToString:@"/livelog"])
    {
        // Create the WebSocket
        WebSocket *webSocket = [[WebSocket alloc] initWithRequest:request socket:asyncSocket];
        
        // Create the WebSocketLogger
        WebSocketLogger *webSocketLogger = [[WebSocketLogger alloc] initWithWebSocket:webSocket];
        
        [[self class] addWebSocketLogger:webSocketLogger];
        return webSocket;
    }
    
    return [super webSocketForURI:path];
}

@end
