package com.fastimage;

import static com.bumptech.glide.request.RequestOptions.signatureOf;

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

import com.bumptech.glide.Priority;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.model.Headers;
import com.bumptech.glide.load.model.LazyHeaders;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.signature.ApplicationVersionSignature;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.NoSuchKeyException;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nullable;

class FastImageViewConverter {
    private static final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);

    private static final Map<String, FastImageCacheControl> FAST_IMAGE_CACHE_CONTROL_MAP =
            new HashMap<String, FastImageCacheControl>() {{
                put("immutable", FastImageCacheControl.IMMUTABLE);
                put("web", FastImageCacheControl.WEB);
                put("cacheOnly", FastImageCacheControl.CACHE_ONLY);
            }};

    private static final Map<String, Priority> FAST_IMAGE_PRIORITY_MAP =
            new HashMap<String, Priority>() {{
                put("low", Priority.LOW);
                put("normal", Priority.NORMAL);
                put("high", Priority.HIGH);
            }};

    // objectFit's scale types. scale-down and none are laid out by the view
    // itself, at the image's own size in dp (see
    // FastImageViewWithUrl.updateImageMatrix).
    private static final Map<String, ImageView.ScaleType> FAST_IMAGE_OBJECT_FIT_MAP =
            new HashMap<String, ImageView.ScaleType>() {{
                put("contain", ScaleType.FIT_CENTER);
                put("cover", ScaleType.CENTER_CROP);
                put("fill", ScaleType.FIT_XY);
                put("scale-down", ScaleType.CENTER_INSIDE);
                put("none", ScaleType.CENTER);
            }};

    // Whether the source has a uri to load (not null, missing or blank).
    static boolean hasUri(@Nullable ReadableMap source) {
        if (source == null || !source.hasKey("uri") || source.isNull("uri")) return false;
        String uri = source.getString("uri");
        return uri != null && !uri.trim().isEmpty();
    }

    // Resolve the source uri to a file path that android understands.
    static @Nullable
    FastImageSource getImageSource(Context context, @Nullable ReadableMap source) {
        if (source == null) return null;
        FastImageSource imageSource = new FastImageSource(context, source.getString("uri"), getHeaders(source));
        imageSource.setWebCache(getCacheControl(source) == FastImageCacheControl.WEB);
        if (source.hasKey("memoryCache") && source.getType("memoryCache") == ReadableType.Boolean) {
            imageSource.setMemoryCache(source.getBoolean("memoryCache"));
        }
        if (source.hasKey("cacheKey") && source.getType("cacheKey") == ReadableType.String) {
            imageSource.setCacheKey(source.getString("cacheKey"));
        }
        return imageSource;
    }

    static Headers getHeaders(ReadableMap source) {
        Headers headers = Headers.DEFAULT;

        if (source.hasKey("headers")) {
            ReadableMap headersMap = source.getMap("headers");
            ReadableMapKeySetIterator iterator = headersMap.keySetIterator();
            LazyHeaders.Builder builder = new LazyHeaders.Builder();

            while (iterator.hasNextKey()) {
                String header = iterator.nextKey();
                String value = headersMap.getString(header);

                builder.addHeader(header, value);
            }

            headers = builder.build();
        }

        return headers;
    }

    static RequestOptions getOptions(Context context, @Nullable FastImageSource imageSource, @Nullable ReadableMap source) {
        // Get priority.
        final Priority priority = FastImageViewConverter.getPriority(source);
        // Get cache control method.
        final FastImageCacheControl cacheControl = FastImageViewConverter.getCacheControl(source);
        DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.AUTOMATIC;
        boolean onlyFromCache = false;
        boolean skipMemoryCache = false;
        switch (cacheControl) {
            case WEB:
                // If using none then OkHttp integration should be used for caching.
                diskCacheStrategy = DiskCacheStrategy.NONE;
                skipMemoryCache = true;
                break;
            case CACHE_ONLY:
                onlyFromCache = true;
                break;
            case IMMUTABLE:
                // Use defaults.
                break;
        }

        RequestOptions options = new RequestOptions()
                .diskCacheStrategy(diskCacheStrategy)
                .onlyRetrieveFromCache(onlyFromCache)
                // memoryCache false: only kept on disk.
                .skipMemoryCache(skipMemoryCache || (imageSource != null && !imageSource.isMemoryCache()))
                .priority(priority)
                .placeholder(TRANSPARENT_DRAWABLE)
                .set(FastImageGlide.REQUEST, true);

        // imageSource is null when only a defaultSource is shown.
        if (imageSource != null && imageSource.isResource()) {
            // Every local resource (drawable) in Android has its own unique numeric id, which are
            // generated at build time. Although these ids are unique, they are not guaranteed unique
            // across builds. The underlying glide implementation caches these resources. To make
            // sure the cache does not return the wrong image, we should clear the cache when the
            // application version changes. Adding a cache signature for only these local resources
            // solves this issue: https://github.com/DylanVann/react-native-fast-image/issues/402
            options = options.apply(signatureOf(ApplicationVersionSignature.obtain(context)));
        }

        return options;
    }

    // The source's cacheKey, or null without one.
    @Nullable
    static String getCacheKey(@Nullable ReadableMap source) {
        if (source == null || !source.hasKey("cacheKey") || source.getType("cacheKey") != ReadableType.String) {
            return null;
        }
        String cacheKey = source.getString("cacheKey");
        return cacheKey == null || cacheKey.isEmpty() ? null : cacheKey;
    }

    static FastImageCacheControl getCacheControl(ReadableMap source) {
        return getValueFromSource("cache", "immutable", FAST_IMAGE_CACHE_CONTROL_MAP, source);
    }

    private static Priority getPriority(ReadableMap source) {
        return getValueFromSource("priority", "normal", FAST_IMAGE_PRIORITY_MAP, source);
    }

    static ScaleType getScaleType(String propValue) {
        return getValue("objectFit", "cover", FAST_IMAGE_OBJECT_FIT_MAP, propValue);
    }

    private static <T> T getValue(String propName, String defaultPropValue, Map<String, T> map, String propValue) {
        if (propValue == null) propValue = defaultPropValue;
        T value = map.get(propValue);
        if (value == null)
            throw new JSApplicationIllegalArgumentException("FastImage, invalid " + propName + " : " + propValue);
        return value;
    }

    private static <T> T getValueFromSource(String propName, String defaultProp, Map<String, T> map, ReadableMap source) {
        String propValue;
        try {
            propValue = source != null ? source.getString(propName) : null;
        } catch (NoSuchKeyException e) {
            propValue = null;
        }
        return getValue(propName, defaultProp, map, propValue);
    }
}
