package common.templates;

import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import io.sphere.sdk.models.Base;
import org.apache.commons.lang3.StringUtils;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.YAMLException;
import play.Logger;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

import static java.util.Objects.requireNonNull;

final class HandlebarsTranslationHelper extends Base implements Helper<String> {
    private final List<String> languages;
    private final List<String> bundles;
    private final Map<String, Map<String, Object>> languageBundleToYamlMap = new HashMap<>();

    public HandlebarsTranslationHelper(final List<String> languages, final List<String> bundles) {
        this.languages = requireNonNull(languages);
        this.bundles = requireNonNull(bundles);
        for (final String language : languages) {
            final List<String> foundBundles = new LinkedList<>();
            final List<String> notFoundBundles = new LinkedList<>();
            for (final String bundle : bundles) {
                try {
                    final Map<String, Object> yamlContent = loadYamlForTranslationAndBundle(language, bundle);
                    languageBundleToYamlMap.put(language + "/" + bundle, yamlContent);
                    foundBundles.add(bundle);
                } catch (final IOException e){
                    notFoundBundles.add(bundle);
                }
            }
            Logger.info("handlebars-i18n: {}: loaded: '{}' failed: {}", language, foundBundles, notFoundBundles);
        }
    }

    @Override
    public CharSequence apply(final String context, final Options options) throws IOException {
        final List<String> languageTags = getLocales(options);
        final String language = languageTags.get(0);//TODO improve
        final String[] parts = StringUtils.split(context, ':');
        final boolean usingDefaultBundle = parts.length == 1;
        final String bundle = usingDefaultBundle ? "translations" : parts[0];
        final boolean containsPlural = containsPlural(options);
        final String key = usingDefaultBundle ? context : parts[1];
        final String resolvedValue = containsPlural ? Optional.ofNullable(resolve(language, bundle, key + "_plural")).orElseGet(() -> resolve(language, bundle, key)) : resolve(language, bundle, key);
        String parametersReplaced = StringUtils.defaultString(resolvedValue);
        for (final Map.Entry<String, Object> entry : options.hash.entrySet()) {
            parametersReplaced = parametersReplaced.replace("__" + entry.getKey() + "__", entry.getValue().toString());
        }
        return parametersReplaced;
    }

    private String resolve(final String language, final String bundle, final String key) {
        final Map<String, Object> yamlContent = languageBundleToYamlMap.getOrDefault(language + "/" + bundle, Collections.emptyMap());
        return resolve(key, yamlContent);
    }

    private String resolve(final String key, final Map<String, Object> yamlContent) {
        final String[] pathSegments = StringUtils.split(key, '.');
        return resolve(yamlContent, pathSegments, 0);
    }

    private boolean containsPlural(final Options options) {
        return options.hash.entrySet().stream()
                .anyMatch(entry -> entry.getValue() instanceof Integer && !((entry.getValue().equals(1))));
    }

    @Nullable
    private static String resolve(final Map<String, Object> yamlContent, final String[] pathSegments, final int index) {
        return Optional.ofNullable(yamlContent.get(pathSegments[index]))
                .map(resolved -> {
                    if (resolved instanceof String) {
                        return (String) resolved;
                    } else if (pathSegments.length == index) {
                        return null;
                    } else if (resolved instanceof Map) {
                        return resolve((Map<String, Object>) resolved, pathSegments, index + 1);
                    } else {
                        return null;
                    }
                })
                .orElse(null);
    }

    private static List<String> getLocales(final Options options) {
        return (List<String>) options.context.get("locales");
    }

    private static Map<String, Object> loadYamlForTranslationAndBundle(final String languageTag, final String bundle) throws IOException {
        final String path = buildYamlPath(languageTag, bundle);
        final InputStream inputStream = getResourceAsStream(path);
        return loadYamlData(inputStream);
    }

    private static String buildYamlPath(final String languageTag, final String bundle) {
        return "META-INF/resources/webjars/locales/" + languageTag + "/" + bundle + ".yaml";
    }

    private static Map<String, Object> loadYamlData(final InputStream inputStream) throws IOException {
        try {
            return (Map<String, Object>) new Yaml().load(inputStream);
        } catch (final YAMLException e) {
            throw new IOException(e);
        }
    }

    private static InputStream getResourceAsStream(final String path) {
        return Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
    }
}

@@ -1923,50 +1923,89 @@
 nal 
-String%5B%5D parts = StringUtils.split
+TranslationIdentifier translationIdentifier = obtainTranslationIdentifier
 (context
 , ':
@@ -2004,13 +2004,8 @@
 text
-, ':'
 );%0A 
@@ -2021,219 +2021,272 @@
 nal 
-boolean usingDefaultBundle = parts.length == 1;%0A        final String bundle = usingDefaultBundle ? %22translations%22 : parts%5B0%5D;%0A        final boolean containsPlural = containsPlural(options
+String bundle = translationIdentifier.bundle;%0A        final String key = translationIdentifier.key;%0A        final String resolvedValue = pluralize(options, language, bundle, key);%0A        return replaceParameters(options, resolvedValue
 );%0A    
+%7D%0A%0A
     
-final
+private
  String 
 key 
@@ -2285,106 +2285,216 @@
 ing 
-key = usingDefaultBundle ? context : parts%5B1%5D;%0A        final String resolvedValue = containsPlural
+pluralize(final Options options, final String language, final String bundle, final String key) %7B%0A        final boolean containsPlural = containsPlural(options);%0A        return (containsPlural)%0A               
  ? O
@@ -2603,16 +2603,32 @@
 e, key))
+%0A               
  : resol
@@ -2646,32 +2646,757 @@
 , bundle, key);%0A
+    %7D%0A%0A    private TranslationIdentifier obtainTranslationIdentifier(final String context) %7B%0A        final String%5B%5D parts = StringUtils.split(context, ':');%0A        final boolean usingDefaultBundle = parts.length == 1;%0A        return new TranslationIdentifier(usingDefaultBundle ? %22translations%22 : parts%5B0%5D, usingDefaultBundle ? context : parts%5B1%5D);%0A    %7D%0A%0A    private static class TranslationIdentifier %7B%0A        private final String key;%0A        private final String bundle;%0A%0A        public TranslationIdentifier(final String bundle, final String key) %7B%0A            this.bundle = bundle;%0A            this.key = key;%0A        %7D%0A    %7D%0A%0A    private String replaceParameters(final Options options, final String resolvedValue) %7B%0A
         String p

Split common.templates.HandlebarsTranslationHelper.apply into smaller methods. #181