package com.zandero.rest.data;

import com.zandero.utils.Assert;
import com.zandero.utils.Pair;
import com.zandero.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.MediaType;
import java.util.*;

/**
 *
 */
public final class MediaTypeHelper {

    private final static Logger log = LoggerFactory.getLogger(MediaTypeHelper.class);

    private MediaTypeHelper() {
        // hide constructor
    }

    public static String getKey(MediaType mediaType) {

        if (mediaType == null) {
            return MediaType.WILDCARD;
        }

        return mediaType.getType() + "/" + mediaType.getSubtype(); // key does not contain any charset
    }

    public static MediaType valueOf(String mediaType) {

        try {
            return parse(mediaType);
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    public static MediaType[] getMediaTypes(String[] value) {

        MediaType[] types = new MediaType[]{};
        for (String item : value) {

            String[] items = item.split(","); // split by "," ...

            for (String single : items) {
                MediaType type = MediaTypeHelper.valueOf(single);
                if (type != null) {
                    types = join(types, type);
                }
            }
        }

        if (types.length == 0) {
            return null;
        }

        return types;
    }

    /**
     * Simple parsing utility to get MediaType
     *
     * @param mediaType to be parsed
     * @return media type of throw IllegalArgumentException if parsing fails
     */
    private static MediaType parse(String mediaType) {

        Assert.notNullOrEmptyTrimmed(mediaType, "Missing media type!");

        String type = null;
        String subType = null;
        Map<String, String> params = new HashMap<>();

        String[] parts = mediaType.split(";");
        for (int i = 0; i < parts.length; i++) {

            String part = StringUtils.trimToNull(parts[i]);
            if (part == null) {
                continue;
            }

            if (i == 0) {
                // get type and subtype
                String[] typeSubType = part.split("/");
                Assert.isTrue(typeSubType.length == 2, "Missing or invalid type with subType: '" + part + "'");

                type = StringUtils.trimToNull(typeSubType[0]);
                subType = StringUtils.trimToNull(typeSubType[1]);

                Assert.notNull(type, "Missing media type!");
                Assert.notNull(subType, "Missing media sub-type!");

            } else {

                try {
                    Pair<String, String> pair = getNameValue(part);
                    params.put(pair.getKey(), pair.getValue());
                } catch (IllegalArgumentException e) {
                    log.warn("Invalid media type option: ", e);
                }
            }
        }

        return new MediaType(type, subType, params);
    }

    private static Pair<String, String> getNameValue(String part) {

        String[] nameValue = part.split("=");
        Assert.isTrue(nameValue.length == 2, "Invalid param format: '" + part + "', expected name=value!");

        String name = StringUtils.trimToNull(nameValue[0]);
        String value = StringUtils.trimToNull(nameValue[1]);

        Assert.notNull(name, "Missing param name: '" + part + "'!");
        Assert.notNull(value, "Missing param value: '" + part + "'!");

        return new Pair<>(StringUtils.trim(nameValue[0]), nameValue[1]);
    }

    public static String toString(MediaType produces) {

        StringBuilder builder = new StringBuilder();
        builder.append(produces.getType()).append("/").append(produces.getSubtype());

        if (produces.getParameters().size() > 0) {
            for (String key : produces.getParameters().keySet()) {
                builder.append(";");
                builder.append(key).append("=").append(produces.getParameters().get(key));
            }
        }

        return builder.toString();
    }

    public static boolean notDefaultMediaType(MediaType[] values) {
        return !isDefaultMediaType(values);
    }

    public static boolean isDefaultMediaType(MediaType[] values) {
        return values != null &&
                   values.length == 1 &&
                   values[0].isWildcardType() &&
                   values[0].isWildcardSubtype();
    }

    public static boolean matches(MediaType desiredMediaType, MediaType producedMediaType) {
        if (desiredMediaType == null) {
            return false;
        }

        return desiredMediaType.isCompatible(producedMediaType);
    }

    public static MediaType[] join(MediaType[] base, MediaType additional) {
        if (additional == null) {
            return base;
        }

        return join(base, new MediaType[]{additional});
    }

    public static MediaType[] join(MediaType[] base, MediaType[] additional) {
        if (additional == null || additional.length == 0) {
            return base;
        }

        if (base == null || base.length == 0) {
            base = additional; // let's make sure we have no duplicates
        }

        Set<MediaType> baseSet = new LinkedHashSet<>(Arrays.asList(base));
        Set<MediaType> addSet = new LinkedHashSet<>(Arrays.asList(additional));

        HashMap<String, MediaType> joinedSet = new LinkedHashMap<>();
        for (MediaType baseItem : baseSet) {

            MediaType found = null;
            for (MediaType addItem : addSet) {
                if (baseItem.getType().equals(addItem.getType()) && baseItem.getSubtype().equals(addItem.getSubtype())) {
                    // join parameters
                    for (String key : addItem.getParameters().keySet()) {
                        String value = addItem.getParameters().get(key);

                        // join parameters ...
                        Map<String, String> params = new HashMap<>(baseItem.getParameters());
                        params.putIfAbsent(key, value);

                        baseItem = new MediaType(baseItem.getType(), baseItem.getSubtype(), params);
                    }

                    found = addItem;
                    break;
                }
            }

            if (found != null) {
                addSet.remove(found);
            }

            joinedSet.put(baseItem.getType() + "/" + baseItem.getSubtype(), baseItem);
        }

        // add remaining from addSet
        for (MediaType addItem: addSet) {
            joinedSet.put(addItem.getType() + "/" + addItem.getSubtype(), addItem);
        }

        return joinedSet.values().toArray(new MediaType[]{});
    }
}