#import <XCTest/XCTest.h>
#import "MWKLanguageLinkController_Private.h"
#import "MWKLanguageLink.h"
#import "MWKDataStore+TemporaryDataStore.h"
#import "NSString+WMFExtras.h"
#import "NSUserDefaults+WMFReset.h"

@interface MWKLanguageLinkControllerTests : XCTestCase
@property (strong, nonatomic) MWKLanguageLinkController *controller;
@property (strong, nonatomic) MWKLanguageFilter *filter;

@end

@implementation MWKLanguageLinkControllerTests

- (void)setUp {
    [super setUp];

    // force language link controller to grab device language, not previous values set by another test
    [[NSUserDefaults standardUserDefaults] wmf_resetToDefaultValues];

    NSAssert([[NSLocale preferredLanguages] containsObject:@"en-US"] || [[NSLocale preferredLanguages] containsObject:@"en"],
             @"For simplicity these tests assume the simulator has 'English' has one of its preferred languages."
              " Instead, these were the preferred languages: %@",
             [NSLocale preferredLanguages]);

    // all tests must start w/ a clean slate
    self.controller = MWKDataStore.temporaryDataStore.languageLinkController;
    self.filter = [[MWKLanguageFilter alloc] initWithLanguageDataSource:self.controller];
    [self.controller resetPreferredLanguages];
}

- (void)testReadPreviouslySelectedLanguagesReturnsEmpty {
    XCTAssertEqual([[self.controller readSavedPreferredLanguageCodes] count], 0);
}

- (void)testDefaultsToDevicePreferredLanguages {
    /*
       since we've asserted above that "en" or "en-US" is one of the OS preferred languages, we can assert that our
       controller contains a language link for "en"
     */
    XCTAssert([[self preferredLanguageCodes] containsObject:@"en"]);
    [self verifyAllLanguageArrayProperties];
}

- (void)testSaveSelectedLanguageUpdatesTheControllersFilteredPreferredLanguages {
    //    NSAssert(![[self preferredLanguageCodes] containsObject:@"test"],
    //             @"'test' shouldn't be a default member of preferred languages!");

    MWKLanguageLink *link = [[MWKLanguageLink alloc] initWithLanguageCode:@"test" pageTitleText:@"test" name:@"test" localizedName:@"test" languageVariantCode:@"test" altISOCode:nil];
    [self.controller appendPreferredLanguage:link];

    XCTAssert([[self preferredLanguageCodes] containsObject:@"test"]);
    [self verifyAllLanguageArrayProperties];
}

- (void)testUniqueAppendToPreferredLanguages {
    MWKLanguageLink *link = [[MWKLanguageLink alloc] initWithLanguageCode:@"test" pageTitleText:@"test" name:@"test" localizedName:@"test" languageVariantCode:@"test" altISOCode:nil];
    [self.controller appendPreferredLanguage:link];
    NSArray *firstAppend = [self.controller.preferredLanguages copy];

    [self.controller appendPreferredLanguage:link];
    NSArray *secondAppend = [self.controller.preferredLanguages copy];

    XCTAssertEqualObjects(firstAppend, secondAppend);

    [self verifyAllLanguageArrayProperties];
}

- (void)testLanguagesPropertiesAreNonnull {
    self.controller = MWKDataStore.temporaryDataStore.languageLinkController;
    XCTAssertTrue(self.controller.allLanguages.count > 0);
    XCTAssertTrue(self.controller.otherLanguages.count > 0);
    XCTAssertTrue(self.controller.preferredLanguages.count > 0);
    [self verifyAllLanguageArrayProperties];
}

- (void)testBasicFiltering {
    self.filter.languageFilter = @"en";

    XCTAssert([self.filter.filteredLanguages wmf_reject:^BOOL(MWKLanguageLink *langLink) {
                  return [langLink.name wmf_caseInsensitiveContainsString:@"en"] || [langLink.localizedName wmf_caseInsensitiveContainsString:@"en"];
              }].count == 0,
              @"All filtered languages have a name or localized name containing filter ignoring case");
    [self verifyAllLanguageArrayProperties];
}

- (void)testEmptyAfterFiltering {
    self.filter.languageFilter = @"$";
    XCTAssert(self.filter.filteredLanguages.count == 0);
}

- (void)testContentLanguageCodeProperty {
    NSString *languageCode = @"zh";
    NSString *languageVariantCode = @"zh-hans";

    // If languageVariantCode is non-nil and non-empty string, contentLanguageCode returns languageVariantCode
    MWKLanguageLink *link = [[MWKLanguageLink alloc] initWithLanguageCode:languageCode pageTitleText:@"test" name:@"test" localizedName:@"test" languageVariantCode:languageVariantCode altISOCode:nil];
    XCTAssertEqualObjects(link.contentLanguageCode, languageVariantCode);

    // If languageVariantCode is nil, contentLanguageCode returns languageCode
    link = [[MWKLanguageLink alloc] initWithLanguageCode:languageCode pageTitleText:@"test" name:@"test" localizedName:@"test" languageVariantCode:nil altISOCode:nil];
    XCTAssertEqualObjects(link.contentLanguageCode, languageCode);

    // If languageVariantCode is an empty string, contentLanguageCode returns languageCode
    link = [[MWKLanguageLink alloc] initWithLanguageCode:languageCode pageTitleText:@"test" name:@"test" localizedName:@"test" languageVariantCode:@"" altISOCode:nil];
    XCTAssertEqualObjects(link.contentLanguageCode, languageCode);
}

- (void)testLanguageVariantCodeURLPropagation {
    NSString *languageCode = @"sr";
    NSString *languageVariantCode = @"sr-el";

    // The languageVariantCode property should propagate to the siteURL.
    MWKLanguageLink *link = [[MWKLanguageLink alloc] initWithLanguageCode:languageCode pageTitleText:@"test" name:@"test" localizedName:@"test" languageVariantCode:languageVariantCode altISOCode:nil];
    NSURL *siteURL = link.siteURL;
    XCTAssertEqualObjects(siteURL.wmf_languageVariantCode, languageVariantCode);

    // The languageVariantCode property should propagate to the articleURL.
    link = [[MWKLanguageLink alloc] initWithLanguageCode:languageCode pageTitleText:@"PageTitle" name:@"test" localizedName:@"test" languageVariantCode:languageVariantCode altISOCode:nil];
    NSURL *articleURL = link.articleURL;
    XCTAssertEqualObjects(articleURL.wmf_languageVariantCode, languageVariantCode);
}

/* Note that this test relies on the OS *not* having Uzbek set as a preferred language.
 * Since there is no way to change OS language settings from a test, to check the fallback
 * setting if a language is not found in the app or the OS preferred languages, the tested
 * language cannot be one of the OS preferred langugages.
 *
 * Choosing Uzbek since it is a less frequently used language and because it has the most
 * fallback choices of any variant-aware language except for Chinese.
 */
- (void)testPreferredLanguageVariantForLanguageCode {
    NSInteger foundIndex = [NSLocale.preferredLanguages indexOfObjectPassingTest:^BOOL(NSString * _Nonnull languageCode, NSUInteger idx, BOOL * _Nonnull stop) {
        return [languageCode hasPrefix:@"uz"];
    }];
    if (foundIndex != NSNotFound) {
        XCTFail(@"Test being run with Uzbek included in OS preferred languages: '%@'", NSLocale.preferredLanguages[foundIndex]);
    }
    
    NSString *chineseLanguageVariantCode = @"zh-my";
    NSString *uzbekLanguageVariantCode = @"uz-latin";

    MWKLanguageLink *link = [[MWKLanguageLink alloc] initWithLanguageCode:@"zh" pageTitleText:@"" name:@"Malaysia Simplified" localizedName:@"大马简体" languageVariantCode:chineseLanguageVariantCode altISOCode:nil];
    
    // Add langugage link and reorder to the front, so that regardless of the
    // system langauge settings, this link is the first found variant for this language
    [self.controller appendPreferredLanguage:link];
    [self.controller reorderPreferredLanguage:link toIndex:0];
    
    // Test finding in app preferences
    NSString *chineseResult = [self.controller preferredLanguageVariantCodeForLanguageCode:@"zh"];
    XCTAssertEqualObjects(chineseResult, chineseLanguageVariantCode);

    // Test fallback not in app preferences or OS preferences
    NSString *uzbekResult = [self.controller preferredLanguageVariantCodeForLanguageCode:@"uz"];
    XCTAssertEqualObjects(uzbekResult, uzbekLanguageVariantCode);

    // Test non-variant languages not found in app or OS preferences
    NSString *englishResult = [self.controller preferredLanguageVariantCodeForLanguageCode:@"en"];
    XCTAssertNil(englishResult);

    NSString *frenchResult = [self.controller preferredLanguageVariantCodeForLanguageCode:@"fr"];
    XCTAssertNil(frenchResult);
}

- (void)testLanguageCodeForISOLanguageCode {
    
    // Test the single case where the ISO language code does not match the Wikipedia language code
    NSString *norwegianISOCode = @"nb";
    NSString *norwegianLanguageCode = @"no";

    NSString *norwegianResult = [MWKLanguageLinkController languageCodeForISOLanguageCode:norwegianISOCode];
    XCTAssertEqualObjects(norwegianResult, norwegianLanguageCode);
    
    // Test that other language codes are returned just as passed in
    // Note that the method does not check if the passed in code is a valid language code
    NSArray<NSString *> *identicalLanguageCodes = @[@"de", @"ja", @"en", @"es", @"zh", @"uz"];
    
    for (NSString *currentLanguageCode in identicalLanguageCodes) {
        NSString *result = [MWKLanguageLinkController languageCodeForISOLanguageCode:currentLanguageCode];
        XCTAssertEqualObjects(result, currentLanguageCode);
    }
    
    // Test that if a nil value is passed in, nil is returned
    NSString *expectingNil = [MWKLanguageLinkController languageCodeForISOLanguageCode:nil];
    XCTAssertNil(expectingNil);
}

- (void)testDuplicateLanguageCodeFiltering {
    // This tests that the first item is the one that remains in an ordered set if there are duplicate items.
    // This ensures that the preferred langauge uniquing code will behave as expected.
    NSArray *array = @[@"en", @"no", @"fr", @"no"];
    NSArray *expectedResult = @[@"en", @"no", @"fr"];
    NSArray *uniquedArray = [[NSOrderedSet orderedSetWithArray:array] array];
    XCTAssertEqualObjects(uniquedArray, expectedResult);
}

#pragma mark - Utils

- (void)verifyAllLanguageArrayProperties {
    [self verifyPreferredAndOtherSumIsAllLanguages];
    [self verifyPreferredAndOtherAreDisjoint];
}

- (void)verifyPreferredAndOtherAreDisjoint {
    XCTAssertFalse([[NSSet setWithArray:self.controller.preferredLanguages]
                       intersectsSet:
                           [NSSet setWithArray:self.controller.otherLanguages]],
                   @"'preferred' and 'other' languages shouldn't intersect: \n preferred: %@ \nother: %@",
                   self.controller.preferredLanguages, self.controller.otherLanguages);
}

- (void)verifyPreferredAndOtherSumIsAllLanguages {
    NSSet *joinedLanguages = [NSSet setWithArray:
                                        [self.controller.preferredLanguages
                                            arrayByAddingObjectsFromArray:self.controller.otherLanguages]];

    XCTAssert(joinedLanguages.count == self.controller.otherLanguages.count + self.controller.preferredLanguages.count);

    XCTAssertEqualObjects([NSSet setWithArray:self.controller.allLanguages], joinedLanguages);
}

- (NSArray<NSString *> *)preferredLanguageCodes {
    return [self.controller.preferredLanguages valueForKey:WMF_SAFE_KEYPATH(MWKLanguageLink.new, languageCode)];
}

- (NSArray *)allLanguageCodes {
    return [self.controller.allLanguages valueForKey:WMF_SAFE_KEYPATH(MWKLanguageLink.new, languageCode)];
}

@end
