#import "HttpRequestOrResponse.h"
#import "Constraints.h"
#import "Util.h"

@implementation HttpRequestOrResponse

+(HttpRequestOrResponse*) httpRequestOrResponse:(id)requestOrResponse {
    require(requestOrResponse != nil);
    
    HttpRequestOrResponse* h = [HttpRequestOrResponse new];
    h->requestOrResponse = requestOrResponse;
    require([h isResponse] || [h isRequest]);
    return h;
}
-(bool) isRequest {
    return [requestOrResponse isKindOfClass:[HttpRequest class]];
}
-(bool) isResponse {
    return [requestOrResponse isKindOfClass:[HttpResponse class]];
}
-(HttpRequest*) request {
    requireState([self isRequest]);
    return requestOrResponse;
}
-(HttpResponse*) response {
    requireState([self isResponse]);
    return requestOrResponse;
}
-(NSData*) serialize {
    return [requestOrResponse serialize];
}

+(HttpRequestOrResponse*) tryExtractFromPartialData:(NSData*)data usedLengthOut:(NSUInteger*)usedLengthPtr {
    require(data != nil);

    // first line should contain HTTP
    checkOperation([data tryFindIndexOf:[@"\r\n" encodedAsAscii]] == nil || [data tryFindIndexOf:[@"HTTP" encodedAsAscii]] != nil);
    // expecting \r\n line endings
    checkOperation(([data tryFindIndexOf:[@"\n" encodedAsAscii]] == nil) == ([data tryFindIndexOf:[@"\r\n" encodedAsAscii]] == nil));
    
    NSNumber* tryHeaderLength = [data tryFindIndexOf:[@"\r\n\r\n" encodedAsUtf8]];
    if (tryHeaderLength == nil) return nil;
    NSUInteger headerLength = [tryHeaderLength unsignedIntegerValue];
    NSString* fullHeader = [[data take:headerLength] decodedAsUtf8];
    headerLength += 4; // account for \r\n\r\n
    
    NSArray* headerLines = [fullHeader componentsSeparatedByString:@"\r\n"];
    checkOperation([headerLines count] >= 1);
    
    //      GET /index.html HTTP/1.1
    //      HTTP/1.1 200 OK
    NSString* requestOrResponseLine = [headerLines objectAtIndex:0];
    NSArray* requestOrResponseLineParts = [requestOrResponseLine componentsSeparatedByString:@" "];
    checkOperation([requestOrResponseLineParts count] >= 3);
    bool isResponse = [[requestOrResponseLineParts objectAtIndex:0] hasPrefix:@"HTTP/"];
    
    //      Host: www.example.com
    //      Content-Length: 5
    NSMutableDictionary* headers = [NSMutableDictionary dictionary];
    for (NSUInteger i = 1; i < [headerLines count]; i++) {
        NSString* headerLine = [headerLines objectAtIndex:i];
        
        NSArray* headerLineParts = [headerLine componentsSeparatedByString:@":"];
        checkOperation([headerLineParts count] >= 2);
        NSString* headerKey = [[headerLineParts objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
        NSString* headerValue = [[headerLine substringFromIndex:[(NSString *)[headerLineParts objectAtIndex:0] length]+1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
        [headers setObject:headerValue forKey:headerKey];
    }
    
    NSString* contextLengthText = [headers objectForKey:@"Content-Length"];
    NSNumber* contentLengthParsed = [contextLengthText tryParseAsUnsignedInteger];
    checkOperation((contextLengthText == nil) == (contentLengthParsed == nil));
    
    bool hasContent = contentLengthParsed != nil;
    NSUInteger contentLength = [contentLengthParsed unsignedIntegerValue];
    if (headerLength + contentLength > [data length]) return nil; // need more data
    NSData* optionalBodyData = hasContent ? [data subdataWithRange:NSMakeRange(headerLength, contentLength)] : nil;
    
    *usedLengthPtr = headerLength + contentLength;
    if (isResponse) {
        NSNumber* statusCodeParsed = [[requestOrResponseLineParts objectAtIndex:1] tryParseAsUnsignedInteger];
        checkOperation(statusCodeParsed != nil);
        
        NSUInteger statusCode = [statusCodeParsed unsignedIntegerValue];
        NSString* statusText = [[requestOrResponseLineParts subarrayWithRange:NSMakeRange(2, [requestOrResponseLineParts count] - 2)] componentsJoinedByString:@" "];
        HttpResponse* response = [HttpResponse httpResponseFromStatusCode:statusCode
                                                            andStatusText:statusText
                                                               andHeaders:headers
                                                      andOptionalBodyData:optionalBodyData];
        return [HttpRequestOrResponse httpRequestOrResponse:response];
    } else {
        checkOperation([requestOrResponseLineParts count] == 3);
        NSString* method = [requestOrResponseLineParts objectAtIndex:0];
        NSString* location = [requestOrResponseLineParts objectAtIndex:1];
        HttpRequest* request = [HttpRequest httpRequestWithMethod:method
                                                      andLocation:location
                                                       andHeaders:headers
                                                  andOptionalBody:[optionalBodyData decodedAsUtf8]];
        return [HttpRequestOrResponse httpRequestOrResponse:request];
    }
}

-(NSString*) description {
    return [requestOrResponse description];
}

@end
