/*
 * This file is part of mpv.
 *
 * mpv is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * mpv is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#endif

#ifdef _WIN32
#include <assert.h>

#include <io.h>
#include <windows.h>
#else
#include <unistd.h>
#endif

#include "libmpv_common.h"

#ifndef F_OK
#define F_OK 0
#endif

#ifdef _WIN32
static bool any_starts_with(const wchar_t *buffer, ULONG count, const wchar_t *str)
{
    for (ULONG pos = 0, i = 0; i < count; ++i) {
        assert(buffer[pos]);
        if (wcsncmp(buffer + pos, str, wcslen(str)) == 0)
            return true;
        pos += wcslen(buffer + pos) + 1;
    }
    return false;
}
#endif

static bool have_english_locale(void)
{
#ifdef __APPLE__
    CFArrayRef arr = CFLocaleCopyPreferredLanguages();
    if (!arr)
        return false;
    CFIndex count = CFArrayGetCount(arr);
    if (!count)
        return false;

    bool ret = false;
    CFRange range = CFRangeMake(0, 2);
    CFStringRef en = CFStringCreateWithCString(NULL, "en", kCFStringEncodingMacRoman);
    for (CFIndex i = 0; i < count; i++) {
        CFStringRef cfstr = CFArrayGetValueAtIndex(arr, i);
        if (CFStringCompareWithOptions(cfstr, en, range, 0) == 0) {
            ret = true;
            break;
        }
    }
    CFRelease(en);
    return ret;
#endif

#ifdef _WIN32
    wchar_t buf[1024];
    ULONG size = _countof(buf);
    ULONG count = 0;

    if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &count, buf, &size))
        fail("GetUserPreferredUILanguages failed: %#lx\n", GetLastError());

    if (any_starts_with(buf, count, L"en"))
        return true;

    if (any_starts_with(buf, count, L"en"))
        return true;

    return false;
#endif
    return true;
}

static void test_track_selection(char *file, char *path)
{
    int ret = access(path, F_OK);
    if (ret)
        fail("Test file, '%s', was not found!\n", path);

    if (strcmp(file, "eng_default.mkv") == 0) {
        // --no-config
        reload_file(path);
        check_string("current-tracks/sub/selected", "yes");

        // --subs-falback=no
        set_property_string("subs-fallback", "no");
        reload_file(path);
        check_string("track-list/2/selected", "no");
    } else if (strcmp(file, "eng_default_forced.mkv") == 0) {
        // --subs-fallback-forced=no
        set_property_string("subs-fallback-forced", "no");
        reload_file(path);
        check_string("current-tracks/sub/selected", "yes");
    } else if (strcmp(file, "eng_forced_matching_audio.mkv") == 0 ||
              (strcmp(file, "eng_forced_matching_audio_region.mkv") == 0)) {
        // select forced track
        reload_file(path);
        check_string("current-tracks/sub/selected", "yes");
    } else if (strcmp(file, "eng_forced_no_matching_audio.mkv") == 0) {
        // forced track should not be selected
        reload_file(path);
        check_string("track-list/2/selected", "no");
    } else if (strcmp(file, "eng_forced_always_audio.mkv") == 0) {
        // forced track should be selected anyway despite no matching audio
        set_property_string("subs-fallback-forced", "always");
        reload_file(path);
        check_string("current-tracks/sub/selected", "yes");
    } else if (strcmp(file, "eng_no_default.mkv") == 0) {
        // track should not be selected
        reload_file(path);
        check_string("track-list/2/selected", "no");

        // --subs-fallback=yes
        set_property_string("subs-fallback", "yes");
        reload_file(path);
        check_string("current-tracks/sub/selected", "yes");
    } else if (strcmp(file, "locale.mkv") == 0 ||
               strcmp(file, "locale_forced.mkv") == 0) {
        // default english subs
        reload_file(path);
        check_string("current-tracks/sub/lang", "eng");

        // default german subs
        set_property_string("subs-match-os-language", "no");
        reload_file(path);
        check_string("current-tracks/sub/lang", "ger");
    } else if (strcmp(file, "locale_complex.mkv") == 0) {
        set_property_string("subs-fallback", "yes");
        set_property_string("subs-match-os-language", "yes");

        // default+forced eng subs to match audio
        set_property_string("alang", "eng");
        reload_file(path);
        check_string("current-tracks/audio/lang", "eng");
        check_string("current-tracks/sub/lang", "eng");
        // among all eng subs, track 4 should be selected
        check_int("current-tracks/sub/id", 4);

        // forced jpn subs to match audio
        set_property_string("alang", "jpn");
        reload_file(path);
        check_string("current-tracks/audio/lang", "jpn");
        check_string("current-tracks/sub/lang", "jpn");

        // default, non-forced ger subs
        set_property_string("alang", "ger");
        reload_file(path);
        check_string("current-tracks/audio/lang", "ger");
        check_string("current-tracks/sub/lang", "ger");

        // default+forced pol subs to match audio
        set_property_string("alang", "pol");
        reload_file(path);
        check_string("current-tracks/audio/lang", "pol");
        check_string("current-tracks/sub/lang", "pol");

        // default+forced pol subs (first default track)
        set_property_string("subs-match-os-language", "no");
        set_property_string("subs-fallback-forced", "always");
        reload_file(path);
        check_string("current-tracks/sub/lang", "pol");

        // default+forced eng subs to match OS lang
        set_property_string("subs-match-os-language", "yes");
        reload_file(path);
        check_string("current-tracks/sub/lang", "eng");
        // among all eng subs, track 4 should be selected
        check_int("current-tracks/sub/id", 4);

        // default, non-forced ger subs
        set_property_string("subs-fallback-forced", "no");
        reload_file(path);
        check_string("current-tracks/sub/lang", "ger");

        // default+forced eng subs to match slang
        set_property_string("slang", "eng");
        reload_file(path);
        check_string("current-tracks/sub/lang", "eng");
        // among all eng subs, track 4 should be selected
        check_int("current-tracks/sub/id", 4);
    } else if (strcmp(file, "multilang.mkv") == 0) {
        // --alang=jpn should select forced jpn subs
        set_property_string("alang", "jpn");
        reload_file(path);
        check_string("current-tracks/audio/lang", "jpn");
        check_string("current-tracks/sub/lang", "jpn");

        // --alang=pol should select default, non-forced ger subs
        set_property_string("alang", "pol");
        reload_file(path);
        check_string("current-tracks/audio/lang", "pol");
        check_string("current-tracks/sub/lang", "ger");

        // --slang=eng and --subs-with-matching-audio should not pick any subs
        set_property_string("alang", "eng");
        set_property_string("slang", "eng");
        set_property_string("subs-with-matching-audio", "no");
        reload_file(path);
        check_string("current-tracks/audio/lang", "eng");
        check_string("track-list/5/selected", "no");
        check_string("track-list/6/selected", "no");
        check_string("track-list/7/selected", "no");
        check_string("track-list/8/selected", "no");

        // --subs-with-matching-audio=forced checks
        set_property_string("subs-with-matching-audio", "forced");
        reload_file(path);
        check_string("current-tracks/audio/lang", "eng");
        check_string("current-tracks/sub/lang", "eng");

        // forced jpn subs should be selected
        set_property_string("alang", "jpn");
        set_property_string("slang", "jpn");
        reload_file(path);
        check_string("current-tracks/audio/lang", "jpn");
        check_string("current-tracks/sub/lang", "jpn");

        // default+forced eng subs should be selected
        set_property_string("alang", "ger");
        set_property_string("slang", "ger");
        reload_file(path);
        check_string("current-tracks/audio/lang", "ger");
        check_string("current-tracks/sub/lang", "eng");

        // eng audio and pol subs should be selected
        set_property_string("alang", "it");
        set_property_string("slang", "pt,it,pol,ger");
        reload_file(path);
        check_string("current-tracks/audio/lang", "eng");
        check_string("current-tracks/sub/lang", "pol");

        // forced jpn subs should be selected
        set_property_string("alang", "ger");
        set_property_string("slang", "jpn,pol");
        set_property_string("subs-with-matching-audio", "yes");
        set_property_string("subs-fallback-forced", "always");
        reload_file(path);
        check_string("current-tracks/audio/lang", "ger");
        check_string("current-tracks/sub/lang", "jpn");

        // default+forced eng subs should be selected
        set_property_string("alang", "jpn");
        set_property_string("slang", "");
        set_property_string("subs-with-matching-audio", "yes");
        set_property_string("subs-fallback-forced", "always");
        reload_file(path);
        check_string("current-tracks/audio/lang", "jpn");
        check_string("current-tracks/sub/lang", "eng");
    } else if (strcmp(file, "multilang2.mkv") == 0) {
        // default jpn subs
        set_property_string("subs-match-os-language", "no");
        set_property_string("alang", "jpn");
        reload_file(path);
        check_string("track-list/3/selected", "yes");

        // forced eng subs
        set_property_string("alang", "eng");
        reload_file(path);
        check_string("track-list/4/selected", "yes");

        // default jpn subs
        set_property_string("subs-fallback-forced", "no");
        reload_file(path);
        check_string("track-list/3/selected", "yes");

        // default eng subs
        set_property_string("slang", "eng");
        reload_file(path);
        check_string("track-list/6/selected", "yes");

        // no subs
        set_property_string("slang", "");
        set_property_string("subs-fallback", "no");
        reload_file(path);
        check_string("track-list/3/selected", "no");
        check_string("track-list/4/selected", "no");
        check_string("track-list/5/selected", "no");
        check_string("track-list/6/selected", "no");

        // untagged eng subs
        set_property_string("sid", "3");
        reload_file(path);
        check_string("track-list/5/selected", "yes");
    } else if (strcmp(file, "multiprogram.ts") == 0) {
        // Program 1
        //    Stream #0:0[0x100]: Video
        //    Stream #0:1[0x101](eng): Subtitle
        //    Stream #0:2[0x104](fra): Subtitle
        // Program 2
        //    Stream #0:3[0x102]: Video
        //    Stream #0:4[0x103](jpn): Subtitle
        //    Stream #0:2[0x104](fra): Subtitle

        set_property_string("subs-match-os-language", "no");
        set_property_string("edition", "1");

        reload_file(path);
        check_string("current-edition", "1");

        // no subs are selected by default
        check_string("track-list/count", "3");
        check_string("track-list/0/type", "sub");
        check_string("track-list/0/lang", "fra");
        check_string("track-list/0/id", "2");
        check_string("track-list/0/program-id", "1");
        check_string("track-list/0/program-ids", "1,2");
        check_string("track-list/0/selected", "no");
        check_string("track-list/1/type", "video");
        check_string("track-list/1/id", "2");
        check_string("track-list/1/program-id", "2");
        check_string("track-list/1/selected", "yes");
        check_string("track-list/2/lang", "jpn");
        check_string("track-list/2/id", "3");
        check_string("track-list/2/program-id", "2");
        check_string("track-list/2/selected", "no");

        set_property_string("slang", "eng");

        // no eng subs in edition 1
        reload_file(path);
        check_string("track-list/count", "3");
        check_string("track-list/0/lang", "fra");
        check_string("track-list/0/id", "2");
        check_string("track-list/0/selected", "no");
        check_string("track-list/1/type", "video");
        check_string("track-list/1/id", "2");
        check_string("track-list/1/selected", "yes");
        check_string("track-list/2/lang", "jpn");
        check_string("track-list/2/id", "3");
        check_string("track-list/2/selected", "no");

        check_string("current-tracks/video/id", "2");
        check_string("current-tracks/video/program-id", "2");

        // eng subs in edition 0
        set_property_string("edition", "0");
        check_string("current-edition", "0");
        check_string("track-list/count", "3");
        check_string("track-list/0/id", "1");
        check_string("track-list/0/program-id", "1");
        check_string("track-list/0/selected", "yes");
        check_string("track-list/1/id", "1");
        check_string("track-list/1/program-id", "1");
        check_string("track-list/1/lang", "eng");
        check_string("track-list/1/selected", "yes");
        check_string("track-list/2/lang", "fra");
        check_string("track-list/2/id", "2");
        check_string("track-list/2/program-ids", "1,2");
        check_string("track-list/2/selected", "no");

        check_string("current-tracks/sub/lang", "eng");
        check_string("current-tracks/sub/id", "1");
        check_string("current-tracks/sub/program-id", "1");
        check_string("current-tracks/video/id", "1");
        check_string("current-tracks/video/program-id", "1");

        // reload should give the same result
        reload_file(path);
        check_string("current-edition", "0");
        check_string("track-list/count", "3");
        check_string("track-list/0/id", "1");
        check_string("track-list/0/program-id", "1");
        check_string("track-list/0/selected", "yes");
        check_string("track-list/1/id", "1");
        check_string("track-list/1/program-id", "1");
        check_string("track-list/1/lang", "eng");
        check_string("track-list/1/selected", "yes");
        check_string("track-list/2/lang", "fra");
        check_string("track-list/2/id", "2");
        check_string("track-list/2/selected", "no");

        check_string("current-tracks/sub/lang", "eng");
        check_string("current-tracks/sub/id", "1");
        check_string("current-tracks/sub/program-id", "1");
        check_string("current-tracks/video/id", "1");
        check_string("current-tracks/video/program-id", "1");

        set_property_string("subs-fallback", "yes");
        set_property_string("edition", "1");
        // fra is selected: both fra and jpn are in-program for program 2, but fra
        // has a lower tid
        check_string("current-edition", "1");
        check_string("track-list/count", "3");
        check_string("track-list/0/lang", "fra");
        check_string("track-list/0/id", "2");
        check_string("track-list/0/selected", "yes");
        check_string("track-list/1/type", "video");
        check_string("track-list/1/id", "2");
        check_string("track-list/1/selected", "yes");
        check_string("track-list/2/lang", "jpn");
        check_string("track-list/2/id", "3");
        check_string("track-list/2/selected", "no");
        check_string("current-tracks/sub/lang", "fra");
        check_string("current-tracks/sub/id", "2");

        check_string("current-edition", "1");
        check_string("track-list/count", "3");
        check_string("track-list/0/lang", "fra");
        check_string("track-list/0/id", "2");
        check_string("track-list/0/program-id", "1");
        check_string("track-list/0/selected", "yes");
        check_string("track-list/1/id", "2");
        check_string("track-list/1/program-id", "2");
        check_string("track-list/1/selected", "yes");

        check_string("current-tracks/sub/lang", "fra");
        check_string("current-tracks/sub/id", "2");
        check_string("current-tracks/sub/program-id", "1");
        check_string("current-tracks/video/id", "2");
        check_string("current-tracks/video/program-id", "2");

        // flatten-editions: all 5 tracks exposed, no edition filtering
        set_property_string("flatten-editions", "yes");
        set_property_string("edition", "auto");
        set_property_string("slang", "");
        set_property_string("subs-fallback", "no");
        reload_file(path);
        check_string("track-list/count", "5");
        check_string("edition-list/count", "0");

        // video from program 1 selected (first video), no sub selected
        check_string("track-list/0/type", "video");
        check_string("track-list/0/id", "1");
        check_string("track-list/0/program-id", "1");
        check_string("track-list/0/selected", "yes");
        check_string("track-list/1/lang", "eng");
        check_string("track-list/1/id", "1");
        check_string("track-list/1/program-id", "1");
        check_string("track-list/2/lang", "fra");
        check_string("track-list/2/id", "2");
        check_string("track-list/2/program-id", "1");
        check_string("track-list/2/program-ids", "1,2");
        check_string("track-list/3/type", "video");
        check_string("track-list/3/id", "2");
        check_string("track-list/3/program-id", "2");
        check_string("track-list/4/lang", "jpn");
        check_string("track-list/4/id", "3");
        check_string("track-list/4/program-id", "2");
        check_string("current-tracks/video/id", "1");
        check_string("current-tracks/video/program-id", "1");
        check_string("sid", "no");

        // eng sub in video 1 is picked when slang=eng
        set_property_string("slang", "eng");
        reload_file(path);
        check_string("current-tracks/video/id", "1");
        check_string("current-tracks/video/program-id", "1");
        check_string("current-tracks/sub/lang", "eng");
        check_string("current-tracks/sub/id", "1");
        check_string("current-tracks/sub/program-id", "1");

        // no matching sub for video 2 with slang=jpn
        set_property_string("vid", "2");
        reload_file(path);
        check_string("sid", "no");

        // jpn sub in video 2 is picked when slang=jpn
        set_property_string("vid", "2");
        set_property_string("slang", "jpn");
        reload_file(path);
        check_string("current-tracks/video/id", "2");
        check_string("current-tracks/video/program-id", "2");
        check_string("current-tracks/sub/lang", "jpn");
        check_string("current-tracks/sub/id", "3");
        check_string("current-tracks/sub/program-id", "2");

        // no matching sub for video 1 with slang=jpn
        set_property_string("vid", "1");
        reload_file(path);
        check_string("sid", "no");

        // fra sub in program 2 is picked when slang=fra
        set_property_string("slang", "fra");
        set_property_string("vid", "1");
        reload_file(path);
        check_string("current-tracks/video/id", "1");
        check_string("current-tracks/video/program-id", "1");
        check_string("current-tracks/sub/lang", "fra");
        check_string("current-tracks/sub/id", "2");
        check_string("current-tracks/sub/program-ids", "1,2");
        set_property_string("vid", "2");
        reload_file(path);
        check_string("current-tracks/video/id", "2");
        check_string("current-tracks/video/program-id", "2");
        check_string("current-tracks/sub/lang", "fra");
        check_string("current-tracks/sub/id", "2");
        check_string("current-tracks/sub/program-ids", "1,2");
    }
}

int main(int argc, char *argv[])
{
    if (argc < 3)
        return 1;

    const char *locale_test_prefix = "locale";
    if (strncmp(argv[1], locale_test_prefix, strlen(locale_test_prefix)) == 0 &&
        !have_english_locale()) {
        printf("Non English language detected. Skipping locale test.\n");
        return 77;
    }

    ctx = mpv_create();
    if (!ctx)
        return 1;

    atexit(exit_cleanup);

    initialize();

    const char *fmt = "================ TEST: %s %s ================\n";
    printf(fmt, "test_track_selection", argv[1]);
    test_track_selection(argv[1], argv[2]);
    printf("================ SHUTDOWN ================\n");

    command_string("quit");
    while (wrap_wait_event()->event_id != MPV_EVENT_SHUTDOWN) {}

    return 0;
}
