/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package org.glassfish.jersey.media.htmljson;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;

import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;

import net.java.html.BrwsrCtx;
import net.java.html.json.Model;
import net.java.html.json.Models;
import net.java.html.json.Property;

/**
 * Implementation of Jersey's message body reader and writer that
 * can handle reading and writing of JSON models generated by {@link Model}
 * annotation provided by
 * <a target="_blank" href="http://bck2brwsr.apidesign.org/javadoc/net.java.html.json/">net.java.html.json</a>
 * library. Include
 * this JAR in your project and you can then use your
 * model classes as Jersey's entities.
 * <p>
 * <pre>
 * {@link Model @Model}(className="Query", properties={
 *   {@link Property @Property}(name="items", type=Item.<b>class</b>, array=true)
 * })
 * <b>class</b> QueryImpl {
 *
 *   {@link Model @Model}(className="Item", properties={
 *     {@link Property @Property}(name="id", type=String.<b>class</b>),
 *     {@link Property @Property}(name="kind", type=Kind.<b>class</b>)
 *   })
 *   <b>class</b> ItemImpl {
 *   }
 *
 *   <b>enum</b> Kind {
 *     GOOD, BAD
 *   }
 *
 *   <b>public static</b> List{@code <Item>} doQuery() {
 *     {@link WebTarget} target = ...;
 *     Query q = target.request(MediaType.APPLICATION_JSON).get().readEntity(Query.<b>class</b>);
 *     return q.getItems();
 *   }
 * }
 * </pre>
 *
 * @author Jaroslav Tulach (jtulach at netbeans.org)
 */
@ServiceProviders({
        @ServiceProvider(service = MessageBodyWriter.class),
        @ServiceProvider(service = MessageBodyReader.class)
})
public final class HtmlJsonProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> {

    @Override
    public boolean isWriteable(Class clazz, Type type, Annotation[] antns, MediaType mt) {
        if (!mt.isCompatible(MediaType.APPLICATION_JSON_TYPE)) {
            return false;
        }
        if (clazz.isArray()) {
            return Models.isModel(clazz.getComponentType());
        }
        if (java.util.List.class.isAssignableFrom(clazz)) {
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) type;
                Type[] args = pt.getActualTypeArguments();
                if (args.length == 1 && args[0] instanceof Class) {
                    return Models.isModel((Class<?>) args[0]);
                }
            }
        }
        return Models.isModel(clazz);
    }

    @Override
    public long getSize(Object t, Class type, Type type1, Annotation[] antns, MediaType mt) {
        return -1;
    }

    @Override
    public void writeTo(Object t, Class type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap mm, OutputStream out)
            throws IOException, WebApplicationException {
        dump(t, out);
    }

    private void dump(Object t, OutputStream out) throws IOException {
        if (t instanceof Object[]) {
            Object[] arr = (Object[]) t;
            out.write('[');
            for (int i = 0; i < arr.length; i++) {
                if (i > 0) {
                    out.write(',');
                }
                dump(arr[i], out);
            }
            out.write(']');
        } else {
            out.write(t.toString().getBytes("UTF-8"));
        }
    }

    @Override
    public boolean isReadable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
        return isWriteable(type, type1, antns, mt);
    }

    @Override
    public Object readFrom(Class<Object> clazz,
                           Type type, Annotation[] antns, MediaType mt,
                           MultivaluedMap<String, String> mm,
                           InputStream in) throws IOException, WebApplicationException {
        BrwsrCtx def = BrwsrCtx.findDefault(HtmlJsonProvider.class);
        if (clazz.isArray()) {
            List<Object> res = new ArrayList<>();
            final Class<?> cmp = clazz.getComponentType();
            Models.parse(def, cmp, in, res);
            Object[] arr = (Object[]) Array.newInstance(cmp, res.size());
            return res.toArray(arr);
        }
        if (clazz.isAssignableFrom(java.util.List.class)
                && type instanceof ParameterizedType
                && ((ParameterizedType) type).getActualTypeArguments().length == 1
                && ((ParameterizedType) type).getActualTypeArguments()[0] instanceof Class) {
            List<Object> res = new ArrayList<>();
            final Class<?> cmp = (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
            Models.parse(def, cmp, in, res);
            return res;
        }
        return Models.parse(def, clazz, in);
    }
}
