#import "SQLiteStorage.h"
#include <sqlite3.h>
@implementation SQLiteStorage


// Database settings
static BOOL logToConsole = TRUE;
static NSString *appstoreDBFileFormat = @"~/Library/wtfjh-%@-%@.db";
static NSString *systemDBFileFormat = @"~/Library/Preferences/wtfjh-%@-@.db";
static const char createTableStmtStr[] = "CREATE TABLE tracedCalls (className TEXT, methodName TEXT, argumentsAndReturnValueDict TEXT,CALLSTACK TEXT)";
static NSString* saveTracedCallStmtStr = @"INSERT INTO tracedCalls VALUES (?1, ?2, ?3,?4)";
static BOOL ApplyCallStack=NO;
static BOOL getBoolFromPreferences(NSString *preferenceValue) {
    NSMutableDictionary *preferences = [[NSMutableDictionary alloc] initWithContentsOfFile:preferenceFilePath];
    id value = [preferences objectForKey:preferenceValue];
    if (value == nil) {
        return NO; // default to YES
    }
    [preferences release];
    BOOL retVal=[value boolValue];
    [value release];
    return retVal;
}

// Internal stuff
static sqlite3_stmt *saveTracedCallStmt;
static sqlite3 *dbConnection;
+(SQLiteStorage *)sharedManager{
    static SQLiteStorage *sharedUtils = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedUtils = [self alloc];
    });
    return sharedUtils;


}

- (SQLiteStorage *)initWithDefaultDBFilePathAndLogToConsole: (BOOL) shouldLog {
    NSString *DBFilePath = nil;
    // Put application name in the DB's filename to avoid confusion
    NSString *appId = [[NSBundle mainBundle] bundleIdentifier];

    //Format Date To NSString
    NSDate *Date=[NSDate date];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd-HH-mm-ss"];
    [dateFormatter setTimeZone:[NSTimeZone localTimeZone]];
    NSString *currentDate=[dateFormatter stringFromDate:Date];
    [dateFormatter release];
    [Date release];

    // Are we monitoring a System app or an App Store app ?
    NSString *appRoot = [@"~/" stringByExpandingTildeInPath];
    if ([appRoot isEqualToString: @"/var/mobile"]) {
        DBFilePath = [NSString stringWithFormat:systemDBFileFormat, appId,currentDate];
    }
    else {
        DBFilePath = [NSString stringWithFormat:appstoreDBFileFormat, appId,currentDate];
    }

    return [self initWithDBFilePath: [DBFilePath stringByExpandingTildeInPath] andLogToConsole: shouldLog];
}


- (SQLiteStorage *)initWithDBFilePath:(NSString *) DBFilePath andLogToConsole: (BOOL) shouldLog {
    self = [super init];
    sqlite3 *dbConn;
    ApplyCallStack=getBoolFromPreferences(@"AddCallStackToDatabase");
    // Open the DB file if it's already there
    if (sqlite3_open_v2([DBFilePath UTF8String], &dbConn, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK) {

	// If not, create the DB file
	if (sqlite3_open_v2([DBFilePath UTF8String], &dbConn, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
       	 	NSLog(@"wtfjhSQLiteStorage - Unable to open database!");
       		return nil;
    	}
	else {
    		// Create the tables in the DB we just created
    		if (sqlite3_exec(dbConn, createTableStmtStr, NULL, NULL, NULL) != SQLITE_OK) {
			NSLog(@"wtfjhSQLiteStorage - Unable to create tables!");
			return nil;
    		}
    	}
    }

    // Prepare the INSERT statement we'll use to store everything
    sqlite3_stmt *statement = nil;
    if (sqlite3_prepare_v2(dbConn, saveTracedCallStmtStr.UTF8String, -1, &statement, NULL) != SQLITE_OK) {
        NSLog(@"wtfjhSQLiteStorage - Unable to prepare statement!");
        return nil;
    }

    saveTracedCallStmt = statement;
    dbConnection = dbConn;
    logToConsole = shouldLog;
    return self;
}


- (BOOL)saveTracedCall: (CallTracer*) tracedCall {
    int queryResult = SQLITE_ERROR;

    // Serialize arguments and return value to an XML plist
    NSData *argsAndReturnValueData = [tracedCall serializeArgsAndReturnValue];
    if (argsAndReturnValueData == nil) {
        NSLog(@"WTFJH SQLiteStorage::saveTraceCall: can't serialize args or return value");
        return NO;
    }
    NSString *argsAndReturnValueStr = [[NSString alloc] initWithData:argsAndReturnValueData encoding:NSUTF8StringEncoding];

    // Do the query; has to be atomic or we get random SQLITE_PROTOCOL errors
    // TODO: this is probably super slow
    @synchronized(appstoreDBFileFormat) {

    	sqlite3_reset(saveTracedCallStmt);
    	sqlite3_bind_text(saveTracedCallStmt, 1, [ [tracedCall className] UTF8String], -1, nil);
    	sqlite3_bind_text(saveTracedCallStmt, 2, [ [tracedCall methodName] UTF8String], -1, nil);
    	sqlite3_bind_text(saveTracedCallStmt, 3, [argsAndReturnValueStr UTF8String], -1, nil);
        if(ApplyCallStack){
        sqlite3_bind_text(saveTracedCallStmt, 4, [[[NSThread callStackSymbols] componentsJoinedByString:@"\n"] UTF8String], -1, nil);
        }
        else{
        sqlite3_bind_text(saveTracedCallStmt, 4, "WTFJH-StackNotEnabled", -1, nil);
        }

        queryResult = sqlite3_step(saveTracedCallStmt);
}

    if (logToConsole) {
        if(ApplyCallStack){
            NSLog(@"\n-----WTFJH Console-----\nCALLED %@ %@\nWITH:\n%@\n---------------\nAND CALLSTACK:%@", [tracedCall className], [tracedCall methodName], [tracedCall argsAndReturnValue],[[NSThread callStackSymbols] componentsJoinedByString:@"\n"]);
        }
        else{
            NSLog(@"\n-----WTFJH Console-----\nCALLED %@ %@\nWITH:\n%@\n---------------", [tracedCall className], [tracedCall methodName], [tracedCall argsAndReturnValue]);
        }
        
    }

    [argsAndReturnValueStr release];

    if (queryResult != SQLITE_DONE) {
        NSLog(@"WTFJH SQLiteStorage - Commit Failed: %x:%s!", queryResult,sqlite3_errstr(queryResult));
    	return NO;
    }
    return YES;
}


- (void)dealloc
{
    sqlite3_finalize(saveTracedCallStmt);
    sqlite3_close(dbConnection);
    [super dealloc];
}


@end


