//  MPDocument+Search.m
//
//  MacPass
//
//  Created by Michael Starke on 25.02.14.
//  Copyright (c) 2014 HicknHack Software GmbH. All rights reserved.
//

#import "MPDocument+Search.h"
#import "MPDocument.h"
#import "MPDocumentWindowController.h"

#import "KPKGroup.h"
#import "KPKEntry.h"

#import "MPFlagsHelper.h"

NSString *const MPDocumentDidEnterSearchNotification  = @"com.hicknhack.macpass.MPDocumentDidEnterSearchNotification";
NSString *const MPDocumentDidChangeSearchFlags        = @"com.hicknhack.macpass.MPDocumentDidChangeSearchFlagsNotification";
NSString *const MPDocumentDidExitSearchNotification   = @"com.hicknhack.macpass.MPDocumentDidExitSearchNotification";

NSString *const MPDocumentDidChangeSearchResults      = @"com.hicknhack.macpass.MPDocumentDidChangeSearchResults";

NSString *const kMPDocumentSearchResultsKey           = @"kMPDocumentSearchResultsKey";


@implementation MPDocument (Search)

#pragma mark Actions

- (void)performFindPanelAction:(id)sender {
  self.hasSearch = YES;
  [[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidEnterSearchNotification object:self];
  [self updateSearch:self];
}

- (void)updateSearch:(id)sender {
  MPDocumentWindowController *windowController = [self windowControllers][0];
  self.searchString = [windowController.searchField stringValue];
  if(NO == self.hasSearch) {
    [[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidEnterSearchNotification object:self];
  }
  self.hasSearch = YES;
  MPDocument __weak *weakSelf = self;
  dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  dispatch_async(backgroundQueue, ^{
    NSArray *results = [weakSelf _findEntriesMatchingCurrentSearch];
    dispatch_sync(dispatch_get_main_queue(), ^{
      weakSelf.selectedEntry = nil;
      [[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidChangeSearchResults object:weakSelf userInfo:@{ kMPDocumentSearchResultsKey: results }];
    });
  });
}

- (void)exitSearch:(id)sender {
  self.searchString = nil;
  self.hasSearch = NO;
  [[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidExitSearchNotification object:self];
}

- (void)toggleSearchFlags:(id)sender {
  static MPEntrySearchFlags oldFlags;
  if(![sender respondsToSelector:@selector(tag)]) {
    return; // We need to read the button tag
  }
  if(![sender respondsToSelector:@selector(state)]) {
    return; // We need to read the button state
  }
  MPEntrySearchFlags toggleFlag = [sender tag];
  MPEntrySearchFlags newFlags = MPEntrySearchNone;
  BOOL isDoublePasswordFlag = (toggleFlag == MPEntrySearchDoublePasswords);
  NSButton *button = sender;
  switch([button state]) {
    case NSOffState:
      toggleFlag ^= MPEntrySearchAllFlags;
      newFlags = isDoublePasswordFlag ? oldFlags : (self.activeFlags & toggleFlag);
      break;
    case NSOnState:
      if(isDoublePasswordFlag) {
        oldFlags = self.activeFlags;
        newFlags = MPEntrySearchDoublePasswords;
      }
      else {
        /* always mask the double passwords in case another button was pressed */
        self.activeFlags &= (MPEntrySearchDoublePasswords ^ MPEntrySearchAllFlags);
        newFlags = self.activeFlags | toggleFlag;
      }
      break;
    default:
      NSAssert(NO, @"Internal state is inconsistent");
      return;
  }
  if(newFlags != self.activeFlags) {
    self.activeFlags = (newFlags == MPEntrySearchNone) ? MPEntrySearchTitles : newFlags;
    [[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidChangeSearchFlags object:self];
    [self updateSearch:self];
  }
}

#pragma mark Search
- (NSArray *)_findEntriesMatchingCurrentSearch {
  /* Filter double passwords */
  if(MPTestFlagInOptions(MPEntrySearchDoublePasswords, self.activeFlags)) {
    __block NSMutableDictionary *passwordToEntryMap = [[NSMutableDictionary alloc] initWithCapacity:100];
    /* Build up a usage map */
    [[self.root childEntries] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
      KPKEntry *entry = obj;
      /* skip entries without passwords */
      if([entry.password length] > 0) {
        NSMutableSet *entrySet = passwordToEntryMap[entry.password];
        if(entrySet) {
          [entrySet addObject:entry];
        }
        else {
          passwordToEntryMap[entry.password] = [NSMutableSet setWithObject:entry];
        }
      }
    }];
    /* check for usage count */
    __block NSMutableArray *doublePasswords = [[NSMutableArray alloc] init];
    [[passwordToEntryMap allKeys] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
      NSSet *entrySet = passwordToEntryMap[obj];
      if([entrySet count] > 1) {
        [doublePasswords addObjectsFromArray:[entrySet allObjects]];
      }
    }];
    return doublePasswords;
  }
  /* Filter using predicates */
  NSArray *predicates = [self _filterPredicatesWithString:self.searchString];
  if(predicates) {
    NSPredicate *fullFilter = [NSCompoundPredicate orPredicateWithSubpredicates:predicates];
    return [[self.root childEntries] filteredArrayUsingPredicate:fullFilter];
  }
  /* No filter, just return everything */
  return [self.root childEntries];
}

- (NSArray *)optionsEnabledInMode:(MPEntrySearchFlags)mode {
  NSArray *allOptions = @[ @(MPEntrySearchUrls), @(MPEntrySearchUsernames),
                           @(MPEntrySearchTitles), @(MPEntrySearchPasswords) ,
                           @(MPEntrySearchNotes), @(MPEntrySearchDoublePasswords) ];
  
  NSIndexSet *indexes = [allOptions indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    MPEntrySearchFlags flag = [obj integerValue];
    return MPTestFlagInOptions(flag, mode);
  }];
  return [allOptions objectsAtIndexes:indexes];
}

- (NSArray *)_filterPredicatesWithString:(NSString *)string{
  NSMutableArray *prediactes = [[NSMutableArray alloc] initWithCapacity:4];
  
  if(MPTestFlagInOptions(MPEntrySearchTitles, self.activeFlags)) {
    [prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.title CONTAINS[cd] %@", string]];
  }
  if(MPTestFlagInOptions(MPEntrySearchUsernames, self.activeFlags)) {
    [prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.username CONTAINS[cd] %@", string]];
  }
  if(MPTestFlagInOptions(MPEntrySearchUrls, self.activeFlags)) {
    [prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.url CONTAINS[cd] %@", string]];
  }
  if(MPTestFlagInOptions(MPEntrySearchPasswords, self.activeFlags)) {
    [prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.password CONTAINS[cd] %@", string]];
  }
  if(MPTestFlagInOptions(MPEntrySearchNotes, self.activeFlags)) {
    [prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.notes CONTAINS[cd] %@", string]];
  }
  return prediactes;
}

@end
