/*
 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.2 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  
 * Please see the License for the specific language governing rights and 
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#import "USBLoggerClass.h"

//================================================================================================
//   Globals
//================================================================================================
//
bool			gSetDebuggerLevel = true;
UInt32			gDebuggerLevel = 5;
bool			gSetDebuggerType = true;
UInt32			gDebuggerType = 2;
bool			gDisableLogging = false;
mach_port_t 		gQPort = 0;
IODataQueueMemory *	gMyQueue = NULL;
io_connect_t		gControllerUserClientPort = 0;
io_connect_t		gKLogUserClientPort = 0;
mach_port_t 		gMasterPort = 0;

FILE *dumpingFile;

@implementation usbLoggerClass

- (IBAction)clearOutput:(id)sender
{
    [usbloggerOutput setString:@""];
}

- (IBAction)filterOutput:(id)sender
{
    NSMutableString *finalString = [[NSMutableString alloc] init];
    [usbloggerFilteredOutput setString:@""];
    
    if ([[filterTextField1 stringValue] length] == 0 && [[filterTextField2 stringValue] length] == 0) {
        // nothing to filter, so show all output
        //[usbloggerFilteredOutput setString:[usbloggerOutput string]];
        [finalString appendString:[usbloggerOutput string]];
    }
    else {
        NSArray *linesToFilter;
        BOOL filter1IsFound, filter2IsFound;
        UInt32 line;
        int searchType = [filterAndOrSelector indexOfSelectedItem];
            
        linesToFilter = [[usbloggerOutput string] componentsSeparatedByString:@"\n"];
        for (line=0; line < [linesToFilter count]; line++) {
            filter1IsFound = (([[linesToFilter objectAtIndex:line] rangeOfString:[filterTextField1 stringValue] options:NSCaseInsensitiveSearch].location != NSNotFound) || [[filterTextField1 stringValue] length] == 0);
            filter2IsFound = (([[linesToFilter objectAtIndex:line] rangeOfString:[filterTextField2 stringValue] options:NSCaseInsensitiveSearch].location != NSNotFound) || [[filterTextField2 stringValue] length] == 0);
            switch (searchType) {
                case 0: // AND the terms
                    if (filter1IsFound && filter2IsFound)
                        [finalString appendString:[NSString stringWithFormat:@"%@\n",[linesToFilter objectAtIndex:line]]];
                    break;
                case 1: // OR the terms
                    if (filter1IsFound || filter2IsFound)
                        [finalString appendString:[NSString stringWithFormat:@"%@\n",[linesToFilter objectAtIndex:line]]];
                    break;
            }
        }
    }
    [usbloggerFilteredOutput replaceCharactersInRange:NSMakeRange([[usbloggerFilteredOutput string] length], 0) withString:finalString];
    [finalString release];
    [[usbloggerFilteredOutput window] makeFirstResponder:usbloggerFilteredOutput];
}

- (IBAction)startLogging:(id)sender
{
    if (!shouldDisplayOutput) {
        shouldDumpToFile = NO;
        if ([usbloggerDumpToFile state])
        {
            NSSavePanel *sp;
            int result;
            NSCalendarDate *currentDate = [NSCalendarDate date];
            
            sp = [NSSavePanel savePanel];
            [sp setRequiredFileType:@"txt"];
            result = [sp runModalForDirectory:NSHomeDirectory() file:@"USB Log"];
            if (result != NSOKButton) {
                return;
            }
            dumpingFile = fopen ([[sp filename] cString],"w");
            shouldDumpToFile = YES;
            
            [currentDate setCalendarFormat:@"%b %d %H:%M:%S"];

            [self outputString:[NSString stringWithFormat:@"%@: Saving output to file %@\n\n",currentDate,[sp filename]] unconditionally:NO];
        }
        [usbloggerDumpToFile setEnabled:NO];
        [startLoggingButton setTitle:@"Stop"];
        [loggingLevelPopup setEnabled:NO];
        shouldDisplayOutput = YES;
        gSetDebuggerLevel = true;
        loggingLevelChanged = (loggingThreadIsRunning && (gDebuggerLevel != ([loggingLevelPopup indexOfSelectedItem] + 1)));
        gDebuggerLevel = [loggingLevelPopup indexOfSelectedItem] + 1;
        if (!loggingThreadIsRunning) {
            [NSThread detachNewThreadSelector: @selector(USBLogger:) toTarget:self withObject: nil];
        }
        else if (loggingLevelChanged) {
            SetDebuggerOptions(gDisableLogging, gSetDebuggerLevel, gDebuggerLevel, gSetDebuggerType, gDebuggerType);
            loggingLevelChanged = NO;
        }
    }
    else
    {
        shouldDisplayOutput = NO;
        if ([usbloggerDumpToFile state])
            fclose (dumpingFile);
        [usbloggerDumpToFile setEnabled:YES];
        [startLoggingButton setTitle:@"Start"];
        [loggingLevelPopup setEnabled:YES];
    }
}

- (IBAction)timeStamp:(id)sender
{
    NSCalendarDate *currentDate;
    NSString *date;
    currentDate = [NSCalendarDate date];
    [currentDate setCalendarFormat:@"%b %d %H:%M:%S"];
    date = [[NSString alloc] initWithFormat:@"\n\t\tееее %@ ееее\n\n", currentDate];

    [self outputString:date unconditionally:YES];
    //[date release];
}

-(void)USBLogger:(id)anObject
{
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
    loggingThreadIsRunning = YES;
    // Create a master port
    //
    require_string( KERN_SUCCESS == IOMasterPort(NULL, &gMasterPort), errExit, "IOMasterPort returned error");
    // Now, let's open the user client to the USB Controller
    //
    require_string( KERN_SUCCESS == [self OpenUSBControllerUserClient], errExit, "Could not open USB user client");
    // If necessary, send configuration information to the logger
    //
    if ( (gSetDebuggerType) || gDisableLogging || gSetDebuggerLevel )
    {
        require_string( KERN_SUCCESS == SetDebuggerOptions(gDisableLogging, gSetDebuggerLevel, gDebuggerLevel, gSetDebuggerType, gDebuggerType), errExit, "Failed to send commands to USB user client");
    }
    // Now, let's start dumping the log!
    //
    require_string( KERN_SUCCESS == [self DumpUSBLog], errExit, "Could not open dump the USB Log");
    
errExit:

    loggingThreadIsRunning = NO;
    shouldDisplayOutput = NO;
    [self CleanUp];
    if ([usbloggerDumpToFile state])
        fclose (dumpingFile);
    [usbloggerDumpToFile setEnabled:YES];
    [startLoggingButton setTitle:@"Start"];
    [loggingLevelPopup setEnabled:YES];
    [pool release];
}

//================================================================================================
//   OpenUSBControllerUserClient
//================================================================================================
//
-(kern_return_t)OpenUSBControllerUserClient
{
    kern_return_t 	kr;
    io_iterator_t 	iter;
    io_service_t	service;

    char *className = "IOUSBController";
        kr = IOServiceGetMatchingServices( gMasterPort,
                                           IOServiceMatching(className ), &iter);
        if(kr != KERN_SUCCESS)
        {
            [self outputString:[NSString stringWithFormat:@"usblogger: [ERR] IOServiceGetMatchingServices for USB Controller returned %x\n", kr] unconditionally:NO];
            return kr;
        }
            while ((service = IOIteratorNext(iter)) != NULL)
            {
                        kr = IOServiceOpen(service, mach_task_self(), 0, &gControllerUserClientPort);
                if(kr != KERN_SUCCESS)
                {
                    [self outputString:[NSString stringWithFormat:@"usblogger: [ERR] Could not IOServiceOpen on USB Controller client %x\n", kr] unconditionally:NO];
                    goto Exit;
                }
                IOObjectRelease(service);
                break;
            }
            // Enable logging
            //
        kr = IOConnectMethodScalarIScalarO( gControllerUserClientPort, kUSBControllerUserClientEnableLogger, 1, 0, 1);

Exit:
            IOObjectRelease(iter);
            return kr;
}

//================================================================================================
//   SetDebuggerOptions
//================================================================================================
//
kern_return_t SetDebuggerOptions( bool disableLogging, bool setLevel, UInt32 level, bool setType, UInt32 type )
{
    kern_return_t	kr = KERN_SUCCESS;
        if ( disableLogging )
            kr = IOConnectMethodScalarIScalarO( gControllerUserClientPort, kUSBControllerUserClientEnableLogger, 1, 0, 0);
            if(kr != KERN_SUCCESS)
            {
                printf("usblogger: [ERR] Could not disable logger (%x)\n", kr);
                return kr;
            }
                if ( setLevel )
                    kr = IOConnectMethodScalarIScalarO( gControllerUserClientPort, kUSBControllerUserClientSetDebuggingLevel, 1, 0, level);
                if(kr != KERN_SUCCESS)
                {
                    printf("usblogger: [ERR] Could not set debugging level (%x)\n", kr);
                    return kr;
                }
                    if ( setType )
                        kr = IOConnectMethodScalarIScalarO( gControllerUserClientPort, kUSBControllerUserClientSetDebuggingType, 1, 0, type);
                    if(kr != KERN_SUCCESS)
                    {
                        printf("usblogger: [ERR] Could not set debugging level (%x)\n", kr);
                        return kr;
                    }
                        return kr;
}

//================================================================================================
//   DumpUSBLog
//================================================================================================
//
-(kern_return_t)DumpUSBLog
{
    io_iterator_t	iter;
    io_service_t	service;
    kern_return_t 	kr;
    kern_return_t 	res;
    vm_size_t 		bufSize;
    UInt32 		memSize;
    unsigned char 	QBuffer[BUFSIZE];
    char 		msgBuffer[BUFSIZE];
    struct timeval 	msgTime;
    struct timeval 	initialTime;
    struct timezone 	tz;
    int 		level, tag;
    char *className = "com_apple_iokit_KLog";

    [self outputString:[NSString stringWithFormat:@"Timestamp Lvl  \tMessage\n--------- ---\t--------------------------------------\n"] unconditionally:NO];
    gettimeofday(&initialTime, &tz);
    kr = IOServiceGetMatchingServices( gMasterPort,
                                        IOServiceMatching(className ), &iter);
    if(kr != KERN_SUCCESS)
    {
        [self outputString:[NSString stringWithFormat:@"usblogger: [ERR] IOMasterPort returned %x\n", kr] unconditionally:NO];
        return kr;
    }
        while ((service = IOIteratorNext(iter)) != NULL)
        {
                    kr = IOServiceOpen(service, mach_task_self(), 0, &gKLogUserClientPort);
            if(kr != KERN_SUCCESS)
            {
                [self outputString:[NSString stringWithFormat:@"usblogger: [ERR] Could not open object %d\n", kr] unconditionally:NO];
                IOObjectRelease(iter);
                return kr;
            }
            IOObjectRelease(service);
            break;
        }
        IOObjectRelease(iter);
        //mach port for IODataQueue
    gQPort = IODataQueueAllocateNotificationPort();
    if(gQPort == MACH_PORT_NULL)
    {
        [self outputString:[NSString stringWithFormat:@"LogUser: [ERR] Could not allocate DataQueue notification port\n"] unconditionally:NO];
        return kIOReturnNoMemory;
    }
        kr = IOConnectSetNotificationPort(gKLogUserClientPort, 0, gQPort, 0);
    if(kr != KERN_SUCCESS)
    {
        [self outputString:[NSString stringWithFormat:@"LogUser: [ERR] Could not set notification port (%x)\n",kr] unconditionally:NO];
        return kIOReturnNoMemory;
    }
        //map memory
    kr = IOConnectMapMemory(gKLogUserClientPort, 0, mach_task_self(), (vm_address_t*)&gMyQueue, &bufSize, kIOMapAnywhere);
    if(kr != KERN_SUCCESS)
    {
        [self outputString:[NSString stringWithFormat:@"LogUser: [ERR] Could not connect memory map\n"] unconditionally:NO];
        return kIOReturnNoMemory;
    }
        //Tell the logger UserClient to activate its data queue
    kr = IOConnectMethodScalarIScalarO(gKLogUserClientPort, 0, 1, 0, Q_ON);
    if(kr != KERN_SUCCESS)
    {
        [self outputString:[NSString stringWithFormat:@"LogUser: [ERR] Could not open data queue\n"] unconditionally:NO];
        return kr;
    }

    while( TRUE )
    {
        //reset size of expected buffer
        memSize = sizeof(msgBuffer);
        //if no data available in queue, wait on port...
        if(!IODataQueueDataAvailable(gMyQueue))
        {
            res = IODataQueueWaitForAvailableData(gMyQueue, gQPort);
            if(res != KERN_SUCCESS)
            {
                [self outputString:[NSString stringWithFormat:@"ERR: [IODataQueueWaitForAvailableData] res"] unconditionally:NO];
                continue;
            }
        }
        //once dequeued check result for errors
        res = IODataQueueDequeue(gMyQueue, (void*)QBuffer, &memSize);
        if(res != KERN_SUCCESS)
        {
            continue;
        }
        //pull in the timestamp stuff and set a null for %s access
        memcpy(&msgTime, QBuffer, _T_STAMP);
        memcpy(&tag, QBuffer+_T_STAMP, _TAG);
        memcpy(&level, QBuffer+_T_STAMP+_TAG, _LEVEL);
        QBuffer[memSize+1] = 0;
        
        [self outputString:[NSString stringWithFormat:@"%5d.%3.3d [%d]\t%.*s\n",(msgTime.tv_sec-initialTime.tv_sec),(msgTime.tv_usec/1000), level, (int)(memSize-_OFFSET), QBuffer+_OFFSET] unconditionally:NO];

    }
    return KERN_SUCCESS;
}

//================================================================================================
//   CleanUp
//================================================================================================
//
-(void)CleanUp
{
    if ( gQPort )
        IOConnectRelease(gQPort);
    if ( gKLogUserClientPort )
    {
        //Tell the logger UserClient to deactivate its data queue
        IOConnectMethodScalarIScalarO(gKLogUserClientPort, 0, 1, 0, Q_OFF);
        if ( gMyQueue )
            IOConnectUnmapMemory(gKLogUserClientPort, 0, mach_task_self(), (vm_address_t)&gMyQueue);
        IOConnectRelease(gKLogUserClientPort);
    }
    if ( gMasterPort )
        mach_port_deallocate(mach_task_self(), gMasterPort);
        if (gControllerUserClientPort)
        {
            IOConnectRelease(gControllerUserClientPort);
        }
}

+(void)CleanUp // So logger can be cleaned up externally (i.e. from the main controller @ Quit);
{
    if ( gQPort )
        IOConnectRelease(gQPort);
    if ( gKLogUserClientPort )
    {
        //Tell the logger UserClient to deactivate its data queue
        IOConnectMethodScalarIScalarO(gKLogUserClientPort, 0, 1, 0, Q_OFF);
        if ( gMyQueue )
            IOConnectUnmapMemory(gKLogUserClientPort, 0, mach_task_self(), (vm_address_t)&gMyQueue);
        IOConnectRelease(gKLogUserClientPort);
    }
    if ( gMasterPort )
        mach_port_deallocate(mach_task_self(), gMasterPort);
    if (gControllerUserClientPort)
    {
        IOConnectRelease(gControllerUserClientPort);
    }
}

- (void)outputString:(NSString *)string unconditionally:(BOOL)unconditionally
{
    if (!shouldDisplayOutput && !unconditionally)
        return;
    if (shouldDumpToFile) {
        fprintf(dumpingFile, [string cString]);
        fflush(dumpingFile);
    }

    // send the output string to the main thread, so it can update the UI
    // It's not a good idea to update NSTextViews from a secondary thread, plus the NSTextView needs time to
    // refresh. That is why we do this.
    [[NSNotificationCenter defaultCenter] postNotificationName:@"com.apple.USBProber.usblogger" object:string];
    return;
}

@end
