/*
 * Copyright (c) 2021 Taner Sener
 *
 * This file is part of FFmpegKit.
 *
 * FFmpegKit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FFmpegKit 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 Lesser General License for more details.
 *
 *  You should have received a copy of the GNU Lesser General License
 *  along with FFmpegKit.  If not, see <http://www.gnu.org/licenses/>.
 */

#import "AbstractSession.h"
#import "AtomicLong.h"
#import "ExecuteCallback.h"
#import "FFmpegKit.h"
#import "FFmpegKitConfig.h"
#import "LogCallback.h"
#import "ReturnCode.h"

int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit = 5000;

static AtomicLong *sessionIdGenerator = nil;

@implementation AbstractSession {
    long _sessionId;
    ExecuteCallback _executeCallback;
    LogCallback _logCallback;
    NSDate* _createTime;
    NSDate* _startTime;
    NSDate* _endTime;
    NSArray* _arguments;
    NSMutableArray* _logs;
    NSRecursiveLock* _logsLock;
    SessionState _state;
    ReturnCode* _returnCode;
    NSString* _failStackTrace;
    LogRedirectionStrategy _logRedirectionStrategy;
}

+ (void)initialize {
    sessionIdGenerator = [[AtomicLong alloc] initWithValue:1];
}

- (instancetype)init:(NSArray*)arguments withExecuteCallback:(ExecuteCallback)executeCallback withLogCallback:(LogCallback)logCallback withLogRedirectionStrategy:(LogRedirectionStrategy)logRedirectionStrategy {
    self = [super init];
    if (self) {
        _sessionId = [sessionIdGenerator getAndIncrement];
        _executeCallback = executeCallback;
        _logCallback = logCallback;
        _createTime = [NSDate date];
        _startTime = nil;
        _endTime = nil;
        _arguments = arguments;
        _logs = [[NSMutableArray alloc] init];
        _logsLock = [[NSRecursiveLock alloc] init];
        _state = SessionStateCreated;
        _returnCode = nil;
        _failStackTrace = nil;
        _logRedirectionStrategy = logRedirectionStrategy;
    }

    return self;
}

- (ExecuteCallback)getExecuteCallback {
    return _executeCallback;
}

- (LogCallback)getLogCallback {
    return _logCallback;
}

- (long)getSessionId {
    return _sessionId;
}

- (NSDate*)getCreateTime {
    return _createTime;
}

- (NSDate*)getStartTime {
    return _startTime;
}

- (NSDate*)getEndTime {
    return _endTime;
}

- (long)getDuration {
    NSDate* startTime = _startTime;
    NSDate* endTime = _endTime;
    if (startTime != nil && endTime != nil) {
        return [[NSNumber numberWithDouble:([endTime timeIntervalSinceDate:startTime]*1000)] longValue];
    }
    
    return 0;
}

- (NSArray*)getArguments {
    return _arguments;
}

- (NSString*)getCommand {
    return [FFmpegKit argumentsToString:_arguments];
}

- (void)waitForAsynchronousMessagesInTransmit:(int)timeout {
    NSDate* expireDate = [[NSDate date] dateByAddingTimeInterval:((double)timeout)/1000];
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    while ([self thereAreAsynchronousMessagesInTransmit] && ([[NSDate date] timeIntervalSinceDate:expireDate] < 0)) {
        dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC));
    }
}

- (NSArray*)getAllLogsWithTimeout:(int)waitTimeout {
    [self waitForAsynchronousMessagesInTransmit:waitTimeout];

    if ([self thereAreAsynchronousMessagesInTransmit]) {
        NSLog(@"getAllLogsWithTimeout was called to return all logs but there are still logs being transmitted for session id %ld.", _sessionId);
    }

    return [self getLogs];
}

- (NSArray*)getAllLogs {
    return [self getAllLogsWithTimeout:AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit];
}

- (NSArray*)getLogs {
    [_logsLock lock];
    NSArray* logsCopy = [_logs copy];
    [_logsLock unlock];
    
    return logsCopy;
}

- (NSString*)getAllLogsAsStringWithTimeout:(int)waitTimeout {
    [self waitForAsynchronousMessagesInTransmit:waitTimeout];

    if ([self thereAreAsynchronousMessagesInTransmit]) {
        NSLog(@"getAllLogsAsStringWithTimeout was called to return all logs but there are still logs being transmitted for session id %ld.", _sessionId);
    }

    return [self getLogsAsString];
}

- (NSString*)getAllLogsAsString {
    return [self getAllLogsAsStringWithTimeout:AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit];
}

- (NSString*)getLogsAsString {
    NSMutableString* concatenatedString = [[NSMutableString alloc] init];

    [_logsLock lock];
    for (int i=0; i < [_logs count]; i++) {
        [concatenatedString appendString:[[_logs objectAtIndex:i] getMessage]];
    }
    [_logsLock unlock];

    return concatenatedString;
}

- (NSString*)getOutput {
    return [self getAllLogsAsString];
}

- (SessionState)getState {
    return _state;
}

- (ReturnCode*)getReturnCode {
    return _returnCode;
}

- (NSString*)getFailStackTrace {
    return _failStackTrace;
}

- (LogRedirectionStrategy)getLogRedirectionStrategy {
    return _logRedirectionStrategy;
}

- (BOOL)thereAreAsynchronousMessagesInTransmit {
    return ([FFmpegKitConfig messagesInTransmit:_sessionId] != 0);
}

- (void)addLog:(Log*)log {
    [_logsLock lock];
    [_logs addObject:log];
    [_logsLock unlock];
}

- (void)startRunning {
    _state = SessionStateRunning;
    _startTime = [NSDate date];
}

- (void)complete:(ReturnCode*)returnCode {
    _returnCode = returnCode;
    _state = SessionStateCompleted;
    _endTime = [NSDate date];
}

- (void)fail:(NSException*)exception {
    _failStackTrace = [NSString stringWithFormat:@"%@", [exception callStackSymbols]];
    _state = SessionStateFailed;
    _endTime = [NSDate date];
}

- (BOOL)isFFmpeg {
    // IMPLEMENTED IN SUBCLASSES
    return false;
}

- (BOOL)isFFprobe {
    // IMPLEMENTED IN SUBCLASSES
    return false;
}

- (void)cancel {
    if (_state == SessionStateRunning) {
        [FFmpegKit cancel:_sessionId];
    }
}

@end
