package org.jboss.resteasy.plugins.providers;


import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.plugins.providers.yaml.i18n.LogMessages;
import org.jboss.resteasy.plugins.providers.yaml.i18n.Messages;
import org.jboss.resteasy.spi.ReaderException;
import org.jboss.resteasy.spi.WriterException;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Provider for YAML &lt;-> Object marshalling. Uses the following mime
 * types:<pre><code>
 *   text/yaml
 *   text/x-yaml
 *   application/x-yaml</code></pre>
 *
 * @author Martin Algesten
 */
@Provider
@Consumes({"text/yaml", "text/x-yaml", "application/x-yaml"})
@Produces({"text/yaml", "text/x-yaml", "application/x-yaml"})
public class YamlProvider extends AbstractEntityProvider<Object> {

    // MessageBodyReader

    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
                           MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException,
            WebApplicationException {

        try {
            LogMessages.LOGGER.debugf("Provider : %s,  Method : readFrom", getClass().getName());
            if (isValidInternalType(type)) {
                return new Yaml().load(entityStream);
            } else {
                CustomClassLoaderConstructor customClassLoaderConstructor = new CustomClassLoaderConstructor(type.getClassLoader());
                return new Yaml(customClassLoaderConstructor).loadAs(entityStream, type);
            }
        } catch (Exception e) {
            LogMessages.LOGGER.debug(Messages.MESSAGES.failedToDecodeYamlMessage(e.getMessage()));
            throw new ReaderException(Messages.MESSAGES.failedToDecodeYaml(), e);
        }
    }

    // MessageBodyWriter
    protected boolean isValidInternalType(Class type) {
        if (List.class.isAssignableFrom(type)
                || Set.class.isAssignableFrom(type)
                || Map.class.isAssignableFrom(type)
                || type.isArray()) {
            return true;
        } else {
            return false;
        }
    }

    protected boolean isValidType(Class type) {
        if (isValidInternalType(type)) {
            return true;
        }
        if (StreamingOutput.class.isAssignableFrom(type)) return false;
        String className = type.getName();
        if (className.startsWith("java.")) return false;
        if (className.startsWith("javax.")) return false;
        if (type.isPrimitive()) return false;

        return true;
    }


    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return isValidType(type);
    }

    public void writeTo(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
                        MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException,
            WebApplicationException {

        try {
            LogMessages.LOGGER.debugf("Provider : %s,  Method : writeTo", getClass().getName());
            entityStream.write(new Yaml().dump(t).getBytes());

        } catch (Exception e) {

            LogMessages.LOGGER.debug(Messages.MESSAGES.failedToEncodeYaml(t.toString()));
            throw new WriterException(e);

        }

    }

}
