package com.getbubblenow.android.repository;

import android.content.Context;
import android.util.Log;

import com.getbubblenow.android.api.enums.LocalizationDataType;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import dev.b3nedikt.restring.PluralKeyword;
import dev.b3nedikt.restring.StringRepository;
import io.reactivex.Completable;
import io.reactivex.observers.DisposableCompletableObserver;


public final class RestringMessageRepository implements StringRepository {

    private static final String PLURALS_PREFIX = "plurals_";
    private static final String PLURAL_ITEM_DELIMITER = "\\|";
    private static final String PLURAL_ITEM_KEY_VALUE_DELIMITER = "=";
    private static final String ONE = "one";
    private static final String OTHER = "other";
    private static final String TAG = "RestringMessageRepository";

    private static final List<Locale> APP_LOCALES = Arrays.asList(Locale.ENGLISH, Locale.US);

    private static volatile RestringMessageRepository instance;
    private final MessageRemoteLoader remoteLoader = new MessageRemoteLoader();
    private final MessageLocalLoader localLoader = new MessageLocalLoader();

    private final Map<String, CharSequence> strings = new HashMap<>();
    private final Map<String, CharSequence[]> stringArrays = new HashMap<>();
    private final Map<String, Map<PluralKeyword, CharSequence>> quantityStrings = new HashMap<>();


    public static RestringMessageRepository getInstance() {
        if (instance == null) {
            synchronized (RestringMessageRepository.class) {
                if (instance == null) {
                    instance = new RestringMessageRepository();
                }
            }
        }
        return instance;
    }

    public MutableLiveData<Boolean> updatePreAuthLocalization(final Context context, final Locale locale) {
        return update(context, locale, LocalizationDataType.PRE_AUTH);
    }

    public MutableLiveData<Boolean> updatePostAuthLocalization(final Context context, final Locale locale) {
        return update(context, locale, LocalizationDataType.POST_AUTH);
    }

    private MutableLiveData<Boolean> update(
            final Context context,
            final Locale locale,
            final LocalizationDataType localizationDataType) {
        final MutableLiveData<Boolean> mutableLiveData = new MutableLiveData<>();

        final MutableLiveData<Map<String, String>> data;
        switch (localizationDataType) {
            case PRE_AUTH:
                data = remoteLoader.updatePreAuth(context, locale);
                break;
            case POST_AUTH:
                data = remoteLoader.updatePostAuth(context, locale);
                break;
            default:
                throw new IllegalStateException("Message localization data type is not defined.");
        }

        data.observe((LifecycleOwner) context, map -> {
            if (map == null || map.isEmpty()) {
                updateFromLocal(context, locale, localizationDataType, mutableLiveData);
            } else {
                processStrings(map, locale);

                saveToLocal(map, context, locale, localizationDataType).subscribe(new DisposableCompletableObserver() {
                    @Override
                    public void onComplete() {
                        Log.d(TAG, "Data is saved to the local storage.");
                    }
                    @Override
                    public void onError(final Throwable e) {
                        Log.e(TAG, "Failed to save data to the local storage. " + e.getMessage(), e);
                    }
                });

                mutableLiveData.postValue(true);
            }
        });
        return mutableLiveData;
    }

    private void updateFromLocal(
            final Context context,
            final Locale locale,
            final LocalizationDataType localizationDataType,
            final MutableLiveData<Boolean> mutableLiveData) {

        final MutableLiveData<Map<String, String>> data;
        switch (localizationDataType) {
            case PRE_AUTH:
                data = localLoader.updatePreAuth(context, locale);
                break;
            case POST_AUTH:
                data = localLoader.updatePostAuth(context, locale);
                break;
            default:
                throw new IllegalStateException("Message localization data type is not defined.");
        }

        data.observe((LifecycleOwner) context, map -> {
            if (map == null || map.isEmpty()) {
                mutableLiveData.postValue(false);
            } else {
                processStrings(map, locale);
                mutableLiveData.postValue(true);
            }
        });
    }

    private Completable saveToLocal(
            final Map<String, String> map,
            final Context context,
            final Locale locale,
            final LocalizationDataType localizationDataType) {
        final Completable saveCompletable;
        switch (localizationDataType) {
            case PRE_AUTH:
                saveCompletable = localLoader.savePreAuth(map, context, locale);
                break;
            case POST_AUTH:
                saveCompletable = localLoader.savePostAuth(map, context, locale);
                break;
            default:
                throw new IllegalStateException("Message localization data type is not defined.");
        }
        return saveCompletable;
    }

    private void processStrings(final Map<String, String> map, final Locale locale) {
        for (final Entry<String, String> entry : map.entrySet()) {
            final String key = entry.getKey();
            if (key.startsWith(PLURALS_PREFIX)) {
                processEntryValue(key.substring(PLURALS_PREFIX.length()), entry.getValue(), locale);
            } else {
                setString(locale, key, entry.getValue());
            }
        }
    }

    private void processEntryValue(final String key, final String value, final Locale locale) {
        final Map<PluralKeyword, CharSequence> map = new HashMap<>();
        final String[] pluralItems = value.split(PLURAL_ITEM_DELIMITER);

        for (final String pluralItem : pluralItems) {
            final int idx = pluralItem.indexOf(PLURAL_ITEM_KEY_VALUE_DELIMITER);
            final String pluralKey = pluralItem.substring(0, idx);
            final String pluralValue = pluralItem.substring(idx + 1);

            switch (pluralKey) {
                case ONE:
                    map.put(PluralKeyword.ONE, pluralValue);
                    break;
                case OTHER:
                    map.put(PluralKeyword.OTHER, pluralValue);
                    break;
            }
        }
        setQuantityString(locale, key, map);
    }


    @Nullable @Override public Map<PluralKeyword, CharSequence> getQuantityString(@NotNull final Locale locale, @NotNull final String s) {
        return quantityStrings.get(s);
    }

    @NotNull @Override public Map<String, Map<PluralKeyword, CharSequence>> getQuantityStrings(@NotNull final Locale locale) {
        return quantityStrings;
    }

    @Nullable @Override public CharSequence getString(@NotNull final Locale locale, @NotNull final String s) {
        return strings.get(s);
    }

    @Nullable @Override public CharSequence[] getStringArray(@NotNull final Locale locale, @NotNull final String s) {
        return stringArrays.get(s);
    }

    @NotNull @Override public Map<String, CharSequence[]> getStringArrays(@NotNull final Locale locale) {
        return stringArrays;
    }

    @NotNull @Override public Map<String, CharSequence> getStrings(@NotNull final Locale locale) {
        return strings;
    }

    @NotNull @Override public Set<Locale> getSupportedLocales() {
        return new HashSet<>(APP_LOCALES);
    }

    @Override public void setQuantityString(@NotNull final Locale locale, @NotNull final String s, @NotNull final Map<PluralKeyword, ? extends CharSequence> map) {
        quantityStrings.put(s, (Map<PluralKeyword, CharSequence>) map);
    }

    @Override public void setQuantityStrings(@NotNull final Locale locale, @NotNull final Map<String, ? extends Map<PluralKeyword, ? extends CharSequence>> map) {
        for (final Entry<String, ? extends Map<PluralKeyword, ? extends CharSequence>> entry : map.entrySet()) {
            quantityStrings.put(entry.getKey(), (Map<PluralKeyword, CharSequence>) entry.getValue());
        }
    }

    @Override public void setString(@NotNull final Locale locale, @NotNull final String s, @NotNull final CharSequence charSequence) {
        strings.put(s, charSequence);
    }

    @Override public void setStringArray(@NotNull final Locale locale, @NotNull final String s, @NotNull final CharSequence[] charSequences) {
        stringArrays.put(s, charSequences);
    }

    @Override public void setStringArrays(@NotNull final Locale locale, @NotNull final Map<String, CharSequence[]> map) {
        for (final Entry<String, CharSequence[]> entry : map.entrySet()) {
            stringArrays.put(entry.getKey(), entry.getValue());
        }
    }

    @Override public void setStrings(@NotNull final Locale locale, @NotNull final Map<String, ? extends CharSequence> map) {
        for (final Entry<String, ? extends CharSequence> entry : map.entrySet()) {
            strings.put(entry.getKey(), entry.getValue());
        }
    }
}
