//
//  PlusOneFinder.m
//  ArmorP3V1
//

#import "PlusOneFinder.h"
#import "IProgress.h"
#import "PSArmorSet.h"
#import "PSSession.h"
#import "Repository.h"
#import "SkillKind.h"
#import "SkillPoint.h"
#import "SkillSet.h"
#import "DecorationSlot.h"
#import "DecorationMatcher.h"
#import "PSMutex.h"

@implementation PlusOneFinder

@synthesize session = _session;
@synthesize searchSkill = _searchSkill;
@synthesize listSearchSkills = _listSearchSkills;
@synthesize matcherCache = _matcherCache;
@synthesize matchedResult = _matchedResult;
@synthesize baseScanned = _baseScanned;
@synthesize seekList = _seekList;
@synthesize isZeroTask = _isZeroTask;

@synthesize useNestedFind = _useNestedFind;
@synthesize wasTimeout = _wasTimeout;
@synthesize onlySkillScan = _onlySkillScan;
@synthesize debug = _debug;

@synthesize CONFLICT = _CONFLICT;
@synthesize NOTHING = _NOTHING;
@synthesize BASE_TRUE = _BASE_TRUE;
@synthesize BASE_FALSE = _BASE_FALSE;

@synthesize seekListMax = _seekListMax;

-(id)initWith:(PSSession*) session {
    self = [super init];
    if (self) {
        Repository* repo = [Repository mainRepository];
        _session = session;
        [_session retain];
        _searchSkill = session.searchSkills;
        [_searchSkill retain];
        _listSearchSkills = [[NSMutableArray alloc]init];
        _matcherCache = [[NSMutableDictionary alloc]init];
        _matchedResult = [[NSMutableDictionary alloc]init];
        _seekList = [[NSMutableArray alloc]init];
        _baseScanned = [[NSMutableDictionary alloc]init];

        [repo.fukugo searchFilter:session :_listSearchSkills :session.searchSkills];
        for (int i = 0; i < repo.skillDB.countOfCategory; ++ i) {
            SkillCategory* cate = [repo.skillDB categoryOfIndex:i];
            for (int j = 0; j < cate.count; ++ j) {
                SkillPoint* p = [cate pointAtIndex: j];
                if (p.skillPoint > 0) {
                    [_seekList addObject:p];
                }
            }
        }
        _seekListMax = _seekList.count;
        _CONFLICT = @"conflict";
        _NOTHING = @"nothing";
        _BASE_TRUE = @"base_true";
        _BASE_FALSE = @"base_false";
        _isZeroTask = FALSE;
        
        _onlySkillScan = TRUE;
        _useNestedFind = FALSE;
    }else {
        [[PSMutex mainMutex] raiseMemoryError];
    }
    [[PSMutex mainMutex] showMemoryAlertIfError];
    return self;
}

-(void)dealloc {
    [_searchSkill release];
    [_session release];
    [_listSearchSkills release];
    [_matcherCache release];
    [_matchedResult release];
    [_seekList release];
    [_baseScanned release];
    [super dealloc];
}

-(void)addToResult: (PSArmorSet*)set : (SkillSet*) skills0 : (NSString*) tag{
    SkillSet* skills = skills0;
    
    NSMutableSet* pool = [_matchedResult objectForKey:skills];
    if (_onlySkillScan) {
        if (_NOTHING == nil) {
            _NOTHING = [[NSMutableSet alloc]init];
        }
        if (pool != _NOTHING) {
            [_matchedResult setObject:_NOTHING forKey:skills];
            //[skills release];
        }
        return;
    }else {
        if (pool == nil) {
            pool = [[NSMutableSet alloc]init];
            [_matchedResult setObject:pool forKey:skills];
            //[skills release];
        }
        
        [pool addObject:set];
    }
}

-(BOOL)quickFindPlusSkills:(NSMutableSet *)ret :(PSArmorSet *)set :(SkillSet *)searchSkills :(NSMutableArray *)prevResult
{
    SkillSet* searchMasked = [[SkillSet alloc]initWithColumn:_searchSkill];
    
    [searchMasked set_all:_searchSkill];
    for (int i = 0; i < searchSkills.count; ++ i) {
        SkillKind* kind = [searchSkills.listKind objectAtIndex:i];
        int point = searchSkills.point[i];
        BOOL range = searchSkills.positive[i];
        
        int x = [searchMasked findByKind:kind];
        if (x >= 0) {
            [searchMasked set:kind :point :range];
        }else {
            [searchMasked set:kind :point :range];
        }
    }
    
    [ret removeAllObjects];

    NSEnumerator* e = [prevResult objectEnumerator];
    SkillSet *skills = [[SkillSet alloc]init];

    while (TRUE) {
        DecorationSlot* slot = [e nextObject];
        if (slot == nil) {
            break;
        }
        SkillSet* skills0 = [[SkillSet alloc]init];
        [slot getFullDecorationsSkills:skills0];

        [set calculateUseList:FALSE];
        [skills0 sum_all:set.setSkills];

        [skills0 getNamedSkillSetForDisplay: skills];
    
        for (int j = 0; j < skills.count; ++ j) {
            SkillKind* kind = [skills.listKind objectAtIndex:j];
            int point1 = skills.point[j];
            int x = [searchMasked findByKind:kind];
            if (x >=0) {
                int point2 = searchMasked.point[x];
                if (point1 != point2) {
                    [ret addObject:skills];
                    break;
                }
            }
        }
        [skills0 release];
    }
    [skills release];
    
    if (ret.count == 0) {
        return FALSE;
    }
        
    BOOL existToPositive = FALSE;
    for (int i = 0; i < searchSkills.count; ++i) {
        int p = searchSkills.point[i];
        if (p < 0 && searchSkills.positive[i] == true && p % 5 != 0) {
            existToPositive = TRUE;
            break;
        }
    }
    if (existToPositive) {
        NSMutableSet* tempSet = [[NSMutableSet alloc]init];
        NSEnumerator* e = [ret objectEnumerator];
        while (TRUE) {
            SkillSet* skill2 = [e nextObject];
            if (skill2 == nil) {
                break;
            }
            SkillSet* skill3 = [[SkillSet alloc]init];
            [skill3 set_all:skill2];
            
            for (int i = 0; i < searchSkills.count; ++ i) {
                SkillKind* k = [searchSkills.listKind objectAtIndex:i];
                int p = searchSkills.point[i];
                if (p < 0 && searchSkills.positive[i] && p%5 != 0) {
                    if ([skill3 indexOfKind:k] < 0) {
                        [skill3 set:k :p :TRUE];
                    }
                }
            }
            [tempSet addObject: skill3];
        }
        [ret removeAllObjects];
        e = [tempSet objectEnumerator];
        while (TRUE) {
            SkillSet* set = [e nextObject];
            if (set == nil) {
                break;
            }
            [ret addObject:set];
        }    
    }
    return TRUE;
}

-(DecorationMatcher *)getMatcherCached:(SkillSet *)skills {
    DecorationMatcher* matcher = [_matcherCache objectForKey:skills];
    if (matcher == nil) {
        PSSession* sess = [[PSSession alloc]init];
        sess.searchHunterType = _session.searchHunterType;
        sess.searchHunterRank = _session.searchHunterRank;
        sess.searchGenderType = _session.searchGenderType;
        sess.searchTownRank = _session.searchTownRank;
        matcher = [[DecorationMatcher alloc]initWithData: sess];
        [sess release];
        [_matcherCache setObject:matcher forKey:skills];
        [matcher release];
        //if (_matcherCache.count >= 500) {
        //    [_matcherCache removeAllObjects];
        //}
    }
    return matcher;
}

-(BOOL)findPlusOne:(PSArmorSet *)set :(SkillSet *)skills :(NSMutableArray *)result :(BOOL)isBase {
    DecorationMatcher* matcher = [self getMatcherCached: skills];
    
    BOOL check = FALSE;
    
    NSMutableArray* tempList = [[NSMutableArray alloc]init];
    NSMutableSet* plusSet = [[NSMutableSet alloc]init];

    if ([matcher canHaveEnoughDecoration:set :TRUE : tempList]) {
        /*if ([self quickFindPlusSkills:plusSet :set :skills :tempList]) {
            NSLog(@"quick %@", plusSet);
            check = TRUE;
            [self addToResult:set :skills: @"1"];
            Repository* repo = [Repository mainRepository];
            SkillDB *skillDB = repo.skillDB;
            
            NSEnumerator* e = [plusSet objectEnumerator];
            while (TRUE) {
                SkillSet* skill2 = [e nextObject];
                if (skill2 == nil) {
                    break;
                }
                
                SkillSet* skill3 = [skill2 skillSetOfChange:skills];
                check = TRUE;
                [self addToResult:set :skill3: @"2"];
                [skill3 release];
            }
        }else*/ {
            check = TRUE;
            [self addToResult:set :skills: @"3"];
        }
    }
    [tempList release];
    [plusSet release];
    
    return check;
}

-(BOOL)findAnyPlusOne:(PSArmorSet *)set :(NSMutableArray *)prevResult :(id<IProgress>)progress {
    BOOL match = FALSE;
    _wasTimeout = FALSE;
    
    if (_isZeroTask && _onlySkillScan) {
        return FALSE;
    }
    
    NSString* x = [_baseScanned objectForKey:set];
    if (x != nil) {
        if ([x isEqual:_BASE_TRUE]) {
            return TRUE;
        }
        if ([x isEqual:_BASE_FALSE]) {
            return FALSE;
        }
        abort();
    }
    
    NSMutableArray* queue = [[NSMutableArray alloc]init];
    NSMutableSet* passed0 = [[NSMutableSet alloc]init];
    NSMutableSet* already = [[NSMutableSet alloc]init];

    if (queue == nil || passed0 == nil || already == nil) {
        [[PSMutex mainMutex] raiseMemoryError];
        [queue release];
        [passed0 release];
        [already release];
        return FALSE;
    }
    
    [queue addObjectsFromArray:_listSearchSkills];
    NSMutableArray* removeList = [[NSMutableArray alloc]init];
    
    while (queue.count != 0) {
        if (progress.cancelFlag) {
            break;
        }
        progress.percent = (_seekListMax - _seekList.count) * 100.0 / _seekListMax;
        NSMutableString* str = [[NSMutableString alloc]init];
        [str appendFormat:@"掘り起こしたスキル=%d/%d", (_seekListMax - _seekList.count), _seekListMax];
        progress.labelText = str;
        [str release];

        if (progress != nil && progress.cancelFlag) {
            break;
        }
        SkillSet* baseSkill = [queue objectAtIndex:(queue.count - 1)];
        [baseSkill retain];
        [queue removeLastObject];
        
        if ([passed0 containsObject:baseSkill]) {
            [baseSkill release];
            continue;
        }
        [passed0 addObject: baseSkill];
        
        if (prevResult == nil || prevResult.count == 0) {
            //[prevResult release];
            DecorationMatcher* matcher = [self getMatcherCached:baseSkill];
            NSMutableArray* tempList = [[NSMutableArray alloc]init];
            if ([matcher canHaveEnoughDecoration:set :TRUE :tempList]) {
                prevResult = tempList;
            }else {
                [baseSkill release];
                continue;
            }
        }else {
            [prevResult retain];
        }

        NSMutableSet* quick = [[NSMutableSet alloc]init];
        [self quickFindPlusSkills:quick :set :baseSkill :prevResult];
        
        if (quick.count != 0) {
            NSEnumerator* e = [quick objectEnumerator];
            while (TRUE) {
                SkillSet* skill2 = [e nextObject];
                if (skill2 == nil) break;
                SkillSet* skill3 = [skill2 skillSetOfChange:baseSkill];
                [self addToResult:set :skill3: @"4"];
                if (!_onlySkillScan || !_useNestedFind) {
                    [queue addObject:skill2];
                }
                
                for (int i = 0; i < skill3.count; ++ i) {
                    SkillPoint* seek = [[SkillPoint alloc]init];
                    seek.skillKind = [skill3.listKind objectAtIndex:i];
                    seek.skillPoint = skill3.point[i];
                    seek.positiveRange = skill3.positive[i];
                    [removeList addObject:seek];
                    [seek release];
                }
                [skill3 release];
                match = TRUE;
            }
        }else {
            if (![DecorationMatcher existMoreSlot:prevResult]) {
                [baseSkill release];
                if (_onlySkillScan) {
                    continue;
                }
                continue;
            }
        }
        
        [quick release];

        //test ZERO
        if (_seekList.count == 0) {
            _isZeroTask = TRUE;
            break;
        }
        for (SkillPoint* seek in _seekList) {
            if (progress.cancelFlag) {
                break;
            }

            int sx = [baseSkill indexOfKind:seek.skillKind];
            if (sx >= 0) {
                if (baseSkill.positive[sx]) {
                    if (baseSkill.point[sx] >= seek.skillPoint) {
                        continue;
                    }
                }else {
                    if (baseSkill.point[sx] <= seek.skillPoint) {
                        continue;
                    }
                }
            }
            SkillSet* skills = [[SkillSet alloc]init];
            [skills set_all: baseSkill];
            [skills set:seek.skillKind :seek.skillPoint :seek.positiveRange];

            @try {
                if ([already containsObject:skills]) {
                    continue;
                }
                [already addObject:skills];
                if (_onlySkillScan) {
                    if ([_matchedResult objectForKey:skills] != nil) {
                        continue;
                    }
                }
                if ([self findPlusOne:set :skills: nil :FALSE]) {
                    match = TRUE;
                    [removeList addObject:seek];
                    if (_onlySkillScan) {
                        continue;
                    }else if (_useNestedFind) {
                        [queue addObject:skills];
                    } else {
                        break;
                    }
                }
            }
            @catch (NSException *exception) {
                NSLog(@"exception %@ %@", exception, [exception callStackSymbols]);
            }
            @finally {
                [skills release];
            }
        }
        for (SkillPoint* p in removeList) {
            [_seekList removeObject: p];
        }
        [removeList removeAllObjects];
        [baseSkill release];
        baseSkill = nil;
        [prevResult release];
        prevResult = nil;
    }
    [queue release];
    [passed0 release];
    [already release];
    return match;
}

-(void)tryFixMinus:(NSMutableArray*) listArmorSet: (NSMutableArray*) slotList {
    NSMutableSet* needReset = [[NSMutableSet alloc]init];
    
    NSEnumerator* e = [_matchedResult keyEnumerator];
    while(TRUE) {
        SkillSet* test = [e nextObject];
        if (test == nil) {
            break;
        }
        for (int i = 0; i < test.count; ++ i) {
            SkillKind* kind = [test.listKind objectAtIndex: i];
            int point = test.point[i];
            BOOL range = test.positive[i];
            if (test.point[i] < 0 && range == FALSE) {
                SkillSet *newTest = [[SkillSet alloc]init];
                [newTest set_all:test];
                [newTest set:kind :point :range];
                [needReset addObject:newTest];
            }
        }
    }
    NSMutableArray *scan = [[NSMutableArray alloc]init];
    for (int i = 0; i < listArmorSet.count; ++ i) {
        PSArmorSet* set = [listArmorSet objectAtIndex:i];
        if ([_baseScanned objectForKey:set] != nil) {
            [scan addObject:set];
        }
    }
    
    NSEnumerator* eskill = [needReset objectEnumerator];
    while (TRUE) {
        SkillSet* test = [eskill nextObject];
        if (test == nil) break;
        
        for (int i = 0; i < scan.count; ++ i) {
            PSArmorSet* set = [scan objectAtIndex:i];
            [self findPlusOne:set :test :slotList :FALSE];
        }
    }
    [scan release];
}

-(void)getResultMap:(NSMutableDictionary*)skillAndArmors:(NSMutableArray*)listArmorSet {    
    NSMutableSet* scan = [[NSMutableSet alloc]init];

    if (listArmorSet == nil) {
        NSEnumerator* e = [_baseScanned keyEnumerator];
        while (TRUE){
            PSArmorSet* set = [e nextObject];
            if (set == nil) break;
            [scan addObject:set];
        }
    }else {
        for (int i = 0; i < listArmorSet.count; ++ i) {
            PSArmorSet* set = [listArmorSet objectAtIndex:i];
            if ([_baseScanned objectForKey:set] != nil) {
                [scan addObject:set];
            }
        }
    }
    
    NSMutableArray* array = [[NSMutableArray alloc]init];
    NSEnumerator* e = [_matchedResult keyEnumerator];
    while (TRUE) {
        SkillSet* skills = [e nextObject];
        if (skills == nil) break;
        [array addObject: skills];
    }
    
    for (SkillSet* skills in array) {
        NSMutableSet* listHit = [_matchedResult objectForKey:skills];
        NSMutableSet* listFound = [skillAndArmors objectForKey:skills];
        
        if (listHit == _NOTHING) {
            [skillAndArmors setObject:_NOTHING forKey:skills];
        }else {
            NSMutableSet *set1, *set2;
            
            if (listHit.count > scan.count) {
                set1 = listHit;
                set2 = scan;
            }
            else {
                set1 = scan;
                set2 = listHit;
            }
            
            NSEnumerator* setE = [set2 objectEnumerator];
            while (TRUE) {
                PSArmorSet* set = [setE nextObject];
                if (set == nil) break;
                if ([set1 containsObject: set]) {
                    if (listFound == nil) {
                        listFound = [[NSMutableSet alloc]init];
                        [skillAndArmors setObject:listFound forKey:skills];
                    }
                    [listFound addObject:set];
                }
            }
        }
    }
    [array release];
    [skillAndArmors removeObjectForKey: _searchSkill];
}

@end
