//
//  LiveStreamHandler.m
//  CyTV
//
//  Created by Andreas Junghans on 01.01.08.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//

#import "LiveStreamHandler.h"
#import "HTTPServer.h"
#import "AppleScript.h"

#define SEM_DEVICEINFO "cytv_sem_deviceinfo"
#define SHM_DEVICEINFO "cytv_shm_deviceinfo"
#define SEM_PREFIX "cytv_sem_"
#define SHM_PREFIX "cytv_shm_"


@implementation LiveStreamHandler

- (id)init {
    if (self = [super init]) {
        _streamSem = (sem_t *)SEM_FAILED;
        _streamShmFd = -1;
        _streamShm = (uint64_t *)-1;
        _currentPacketIndex = 0xffffffffffffffffll;
        _extensionWords = 0xffffffffffffffffll;
    }
    return self;
}

- (void)dealloc {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(writeData) object:nil];
    if (_streamShm != (uint64_t *)-1) {
        munmap(_streamShm, _streamShmSize);
    }
    if (_streamShmFd != -1) {
        close(_streamShmFd);
    }
    if (_streamSem != (sem_t *)SEM_FAILED) {
        sem_close(_streamSem);
    }
    [super dealloc];
}

- (void)handleRequest {
    int error = 0;
    
    // find out the device id to use
    sem_t *deviceInfoSem = sem_open(SEM_DEVICEINFO, 0);
    if (deviceInfoSem == (sem_t *)SEM_FAILED) {
        error = 1;
    } else {
        sem_wait(deviceInfoSem);
    }
    int deviceInfoShmFd = -1;
    if (!error) {
        deviceInfoShmFd = shm_open(SHM_DEVICEINFO, O_RDONLY, 0);
        if (deviceInfoShmFd == -1) {
            error = 1;
        }
    }
    struct stat st;
    if (!error) {
        if (fstat(deviceInfoShmFd, &st) == -1) {
            error = 1;
        }
    }
    uint64_t *deviceInfoShm = (uint64_t *)-1;
    if (!error) {
        deviceInfoShm = mmap(0, st.st_size, PROT_READ, MAP_SHARED, deviceInfoShmFd, 0);
        if (deviceInfoShm == (uint64_t *)-1) {
            error = 1;
        }
    }
    uint64_t deviceId;
    if (!error) {
        uint64_t extensionWords = deviceInfoShm[0];
        if (extensionWords == 0xffffffffffffffffll || deviceInfoShm[1] == 0) {
            error = 1;
        } else {
            deviceId = deviceInfoShm[extensionWords + 2];
        }
    }
    
    if (deviceInfoShm != (uint64_t *)-1) {
        munmap(deviceInfoShm, st.st_size);
    }
    if (deviceInfoShmFd != -1) {
        close(deviceInfoShmFd);
    }
    if (deviceInfoSem != (sem_t *)-1) {
        sem_post(deviceInfoSem);
        sem_close(deviceInfoSem);
    }
    
    // open the device shm
    if (!error) {
        char semName[50];
        sprintf(semName, "%s%.16llx", SEM_PREFIX, (uint64_t)deviceId);
        _streamSem = sem_open(semName, 0);
        if (_streamSem == (sem_t *)SEM_FAILED) {
            error = 1;
        } else {
            sem_wait(_streamSem);
        }
    }
    if (!error) {
        char shmName[50];
        sprintf(shmName, "%s%.16llx", SHM_PREFIX, (uint64_t)deviceId);
        _streamShmFd = shm_open(shmName, O_RDONLY, 0);
        if (_streamShmFd == -1) {
            error = 1;
        }
    }
    if (!error) {
        if (fstat(_streamShmFd, &st) == -1) {
            error = 1;
        } else {
            _streamShmSize = st.st_size;
        }
    }
    if (!error) {
        _streamShm = mmap(0, _streamShmSize, PROT_READ, MAP_SHARED, _streamShmFd, 0);
        if (_streamShm == (uint64_t *)-1) {
            error = 1;
        }
    }
    if (!error) {
        _extensionWords = _streamShm[0];
        if (_extensionWords == 0xffffffffffffffffll) {
            error = 1;
        }
    }
    
    if (_streamSem != (sem_t *)SEM_FAILED) {
        sem_post(_streamSem);
    }

    if (error) {
        [ResponseHandler writeResponseWithCode:404 content:@"Not found" socket:_asyncSocket timeout:WRITE_ERROR_TIMEOUT];
    } else {
        CFHTTPMessageRef response = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
        NSString *fromTranscodeString = [_paramsDict objectForKey:@"fromTranscode"];
        if (fromTranscodeString != nil) {
            NSComparisonResult result = [fromTranscodeString compare:@"true" options:NSCaseInsensitiveSearch];
            if (result == NSOrderedSame) {
                _fromTranscode = TRUE;
            }
        }
        if (_fromTranscode) {
            CFHTTPMessageSetHeaderFieldValue(response, CFSTR("Content-Length"), CFSTR("9223372036854775807"));
        } else {
            CFHTTPMessageSetHeaderFieldValue(response, CFSTR("Transfer-Encoding"), CFSTR("chunked"));
        }
        CFHTTPMessageSetHeaderFieldValue(response, CFSTR("Content-Type"), CFSTR("video/mpeg"));
        NSData *responseData = [(NSData *)CFHTTPMessageCopySerializedMessage(response) autorelease];
        [_asyncSocket writeData:responseData withTimeout:WRITE_BODY_TIMEOUT tag:HTTP_PARTIAL_RESPONSE];
        CFRelease(response);
    }
}

- (void)writeData {
    // FIXME: double check all access using the shm length
    if (_extensionWords == 0xffffffffffffffffll) {
        return;
    }
    if (_noDataCount == 50) {
        printf("Trying to start live TV\n");
        ++_noDataCount;     // make sure we don't execute this code again
        @try {
            [AppleScript executeScript:@"tell application \"EyeTV\"\nclose every window\nplay\npause\nend tell"];
        } @catch (NSException *e) {
        }
    }
    if (_currentPacketIndex == 0xffffffffffffffffll) {
        sem_wait(_streamSem);
        _extensionWords = _streamShm[0];
        _currentPacketIndex = _streamShm[2];
        _bufferRounds = _streamShm[3];
        sem_post(_streamSem);
        if (_extensionWords != 0xffffffffffffffffll) {
            ++_noDataCount;
            [self performSelector:@selector(writeData) withObject:nil afterDelay:0.1];
        }
    } else {
        NSData *dataToWrite = nil;
        sem_wait(_streamSem);
        _extensionWords = _streamShm[0];
        if (_extensionWords != 0xffffffffffffffffll) {
            uint64_t newPacketIndex = _streamShm[2];
            if (newPacketIndex < _currentPacketIndex) {
                //printf("Writing end of buffer\n");
                dataToWrite = [NSData dataWithBytes:((uint8_t *)_streamShm) + (4 + _extensionWords)*8 + _currentPacketIndex*188
                                             length:(_streamShm[1] - _currentPacketIndex)*188];
                _currentPacketIndex = 0;
            } else if (newPacketIndex > _currentPacketIndex) {
                //printf("Writing some part of the buffer\n");
                dataToWrite = [NSData dataWithBytes:((uint8_t *)_streamShm) + (4 + _extensionWords)*8 + _currentPacketIndex*188
                                             length:(newPacketIndex - _currentPacketIndex)*188];
                _currentPacketIndex = newPacketIndex;
            }
        }
        sem_post(_streamSem);
        if (_extensionWords != 0xffffffffffffffffll) {
            if (dataToWrite == nil) {
                //printf("Waiting for data\n");
                ++_noDataCount;
                [self performSelector:@selector(writeData) withObject:nil afterDelay:0.1];
            } else {
                //_noDataCount = 0;     // just leave it that way, in order to avoid ugly effects - just start a new live steam to trigger it again
                if (_fromTranscode) {
                    [_asyncSocket writeData:dataToWrite withTimeout:WRITE_BODY_TIMEOUT tag:HTTP_PARTIAL_RESPONSE];
                } else {
                    // write chunk length
                    char chunkLength[50];
                    //printf("Chunk length: %llx\n", (uint64_t)[dataToWrite length]);
                    //printf("First bytes: %0lx\n", *((uint32_t *)[dataToWrite bytes]));
                    sprintf(chunkLength, "%llx\r\n", (uint64_t)[dataToWrite length]);
                    [_asyncSocket writeData:[NSData dataWithBytes:chunkLength length:strlen(chunkLength)]
                                withTimeout:WRITE_BODY_TIMEOUT tag:HTTP_PARTIAL_RESPONSE];
                    [_asyncSocket writeData:dataToWrite withTimeout:WRITE_BODY_TIMEOUT tag:HTTP_PARTIAL_RESPONSE];
                    [_asyncSocket writeData:[NSData dataWithBytes:"\r\n" length:2] withTimeout:WRITE_BODY_TIMEOUT tag:HTTP_PARTIAL_RESPONSE];
                }
            }
        }
    }
    if (_extensionWords == 0xffffffffffffffffll) {
        printf("Disconnecting\n");
        [_asyncSocket disconnect];
    }
}

- (void)onSocketCanWrite:(AsyncSocket *)sock {
    [self writeData];
}

@end
