#import "AqBrowserView.h"
#import "AqIPFilter.h"
#import "AqQuery.h"
#import "AqQueryDataSource.h"
#import "AqQueryResponse.h"
#import "AqRemoteFile.h"
#import "DWCustomizableTableView.h"
#import "DWSplitView.h"
#import "DWTableScrollView.h"
#import "NSStringExtensions.h"


@implementation AqQuery

/* Initialization */

    - (id) initWithString: (NSString*) theString; {
        string = [theString retain];
        [self parseQueryString];

        cell = [[AqQueryCell alloc] init];
          [cell setPrimary: [[[DWTableString alloc] initWithString: theString attributes: [AqQueryCell primaryAttributes]] autorelease]];
          
        return [self init];
    }
    
    - (id) init; {
        files = [[NSMutableArray alloc] init];
        hashToFile = [[NSMutableDictionary alloc] init];
        uniqueDescriptors = [[NSMutableSet alloc] init];

        state = kAqQueryControllerNotActive;
        dataSource = [[AqQueryDataSource alloc] initWithQuery: self];

        keepInDrawer = YES;

        [self initView];

        return [super init];
    }

    - (void) initView; 
    {
        id tableView;
        id scrollView;
        
        NSRect f1 = {{0,0},{99,200}};
        NSRect f2 = {{0,0},{99,500}};
        
        tableView = [[[DWCustomizableTableView alloc] init] autorelease];

        scrollView = [[[DWTableScrollView alloc] initWithFrame: f2] autorelease];
            [scrollView setHasVerticalScroller: YES];
            [scrollView setDocumentView: tableView];
        
        [dataSource setTableView: tableView];
        
        browserView = [[AqBrowserView alloc] initWithFrame: f1];
            [browserView setRootNode: [dataSource rootNode] inColumn: 0];
            [browserView setDataSource: dataSource];
            
        splitView = [[DWSplitView alloc] init];
            [splitView addSubview: browserView];
            [splitView addSubview: scrollView];
    }
    
    - (void) dealloc; 
    {
        //NSLog(@"AqQuery: dealloc");
        
        [string release];
        [positive release];
        [negative release];
        [neutral release];
        
        [files release];
        [hashToFile release];
        [uniqueDescriptors release];

        [dataSource release];
        [cell release];
        [splitView release];
        [browserView release];

        [super dealloc];
    }


#pragma mark -
/* Overrides */

    - (BOOL) isEqual: (id) obj; {
        return [string isEqual: obj];
    }
    
    - (unsigned) hash; {
        return [string hash];
    }

    - (NSString*) description; {
        return [NSString stringWithFormat: @"<\"%@\">", string];
    }
    

#pragma mark -
/* Querying */

    - (void) sendQuery; {
        [AqCoreController writeString: [NSString stringWithFormat: @"query|%@\n", [self queryString]]];
    }

    - (void) handleQueryReply: (id) theInfo intendedForMe: (BOOL) forMe; 
    {
        /* check against positive & negative filters */
        
            int i, count;
            id theName = [theInfo objectAtIndex: 9];
            
            /* - */
            
                count = [negative count];
                for (i=0; i<count; i++) {
                    if ([theName containsSubstringCaseInsensitive: [negative objectAtIndex: i]]) {
                        //NSLog(@"- fail: %@", theName);
                        return;
                    }
                }

            /* + */
            
                count = [positive count];
                for (i=0; i<count; i++) {
                    if (![theName containsSubstringCaseInsensitive: [positive objectAtIndex: i]]) {
                        //NSLog(@"+ fail: %@", theName);
                        return;
                    }
                }
                
            /* neutral */
                
                #if 0
                if (!forMe) {
                    count = [neutral count];
                    for (i=0; i<count; i++) {
                        if (![theName containsSubstringCaseInsensitive: [neutral objectAtIndex: i]]) {
                            //NSLog(@"? fail: %@", theName);
                            return;
                        }
                    }
                }
                #endif
                            
        /* actually allocate the query response and add it */

            id response = [[[AqQueryResponse alloc] initWithInfo: theInfo] autorelease];
            
            /* determine if we've seen this file from this host before */
            
                id uniqueDescriptor = [response uniqueDescriptor];
                if (![uniqueDescriptors containsObject: uniqueDescriptor]) {
                    [uniqueDescriptors addObject: uniqueDescriptor];
                } else {
                    //NSLog(@"ignoring response: %@", uniqueDescriptor);
                    //[response release];
                    return;
                }

            /* otherwise, add it */
            
                id file;
                id sha1 = [response uniqueFileDescriptor];
        
                if (!sha1 || !(file = [hashToFile objectForKey: sha1])) 
                {
                    file = [[[AqRemoteFile alloc] init] autorelease];
                    [files addObject: file];
    
                    if (sha1) [hashToFile setObject: file forKey: sha1];
                }
        
                [file addResponse: response];
                
                //if (![[file sha1] isEqual: sha1])
                //    NSLog(@"adding: %@ to %@", [file sha1], sha1);
                
                [dataSource updateFile: file];
                
                //[response release];
        
        #ifdef DEBUG
        NSLog(@"handleQueryReply: %@", files);
        #endif
    }
    
   
#pragma mark -
/* Query String */

    /* similar to AqKeywordFilter */

    - (void) parseQueryString;
    {
        [positive release];
        [negative release];
        [neutral  release];
        
        positive = [[NSMutableArray alloc] init];
        negative = [[NSMutableArray alloc] init];
        neutral  = [[NSMutableArray alloc] init];
    
        id frags = [string componentsSeparatedByString: @" "];
        BOOL allPositive = [defaults boolForKey: @"kAqPositiveFilter"];

        unsigned i;
        for (i=0; i<[frags count]; i++) 
        {
            id frag = [frags objectAtIndex: i];
            
            if (![frag isEqual: @""]) {
                if ([frag hasPrefix: @"-"]) {
                    if (![frag isEqual: @"-"] && [frag characterAtIndex: 1] != '-') {
                        [negative addObject: [frag substringFromIndex: 1]];
                    }
                } else if ([frag hasPrefix: @"+"]) {
                    if (![frag isEqual: @"+"] && [frag characterAtIndex: 1] != '+') {
                        [positive addObject: [frag substringFromIndex: 1]];
                    }
                } else if (allPositive) {
                    [positive addObject: frag];
                } else {
                    [neutral addObject: frag];
                }
            }
        }

        if (![positive count]) {
            [positive release];
            positive = nil;
        }
        
        if (![negative count]) {
            [negative release];
            negative = nil;
        }

        //NSLog(@"+ %@", positive);
        //NSLog(@"- %@", negative);
        //NSLog(@"? %@", neutral);
    }

    - (id) queryString; {
        [self parseQueryString];
        id keywords = [[[NSMutableArray alloc] init] autorelease];
        [keywords addObjectsFromArray: positive];
        [keywords addObjectsFromArray: neutral];
        return [keywords componentsJoinedByString: @" "];
    }


#pragma mark -
/* API */
    
    - (id) string; {
        return string;
    }

    - (id) defaultsString; {
        return [self string];
    }

    - (unsigned) count; {
        return [files count];
    }

    - (id) dataSource; {
        return dataSource;
    }
    
    - (void) removeAllObjects; {
        [dataSource removeAllObjects];
            [browserView setRootNode: [dataSource rootNode] inColumn: 0];
            [browserView updateTables];
        [files removeAllObjects];
        [hashToFile removeAllObjects];
        [uniqueDescriptors removeAllObjects];
    }

@end