/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor  license  agreements.  See the NOTICE file distributed
 * with this work  for  additional  information  regarding  copyright
 * ownership.  The ASF  licenses  this file to you under  the  Apache
 * License, Version 2.0 (the "License"); you may not  use  this  file
 * except in compliance with the License.  You may obtain  a copy  of
 * the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless  required  by  applicable law  or  agreed  to  in  writing,
 * software  distributed  under  the  License  is  distributed  on an
 * "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR  CONDITIONS  OF ANY KIND,
 * either  express  or implied.  See  the License  for  the  specific
 * language governing permissions and limitations under  the License.
 */
package org.apache.clerezza.jaxrs.sparql.providers;

import org.apache.clerezza.*;
import org.apache.clerezza.sparql.ResultSet;
import org.apache.clerezza.sparql.SolutionMapping;
import org.apache.clerezza.sparql.query.Variable;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;

/**
 * MessageBodyWirter for <code>ResultSet</code>.
 * Resulting output conforms to:
 * http://www.w3.org/TR/2007/NOTE-rdf-sparql-json-res-20070618/
 *
 * @author misl
 */
@Component(service = Object.class, property = {"javax.ws.rs=true"})
@Produces({"application/json", "application/sparql-results+json"})
@Provider
@SuppressWarnings("unchecked")
public class ResultSetJsonMessageBodyWriter implements MessageBodyWriter<ResultSet> {

    final Logger logger = LoggerFactory.getLogger(ResultSetJsonMessageBodyWriter.class);

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations,
                               MediaType mediaType) {
        return ResultSet.class.isAssignableFrom(type);
    }

    @Override
    public long getSize(ResultSet t, Class<?> type, Type genericType,
                        Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

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

        JSONObject json = toJsonSource(resultSet);
        entityStream.write(json.toJSONString().getBytes("UTF-8"));
    }

    /**
     * Helper: transforms a {@link ResultSet} or a {@link Boolean} to a
     * json object.
     *
     * @param queryResult
     */
    private JSONObject toJsonSource(ResultSet queryResult) {
        JSONObject root = new JSONObject();
        JSONObject head = new JSONObject();
        root.put("head", head);
        createVariables(queryResult.getResultVars(), head);

        JSONObject results = new JSONObject();
        root.put("results", results);

        JSONArray bindings = null;
        while (queryResult.hasNext()) {
            if (bindings == null) {
                bindings = new JSONArray();
                results.put("bindings", bindings);
            }
            bindings.add(createResult(queryResult.next()));
        }

        return root;
    }

    /**
     * Helper: creates value element from {@link RDFTerm} depending on its
     * class
     */
    private JSONObject createResultElement(RDFTerm resource) {
        JSONObject element = new JSONObject();
        if (resource instanceof IRI) {
            element.put("type", "uri");
            element.put("value", IRI.class.cast(resource).getUnicodeString());
        } else if (resource instanceof Literal) {
            element.put("type", "literal");
            element.put("value", Literal.class.cast(resource).getLexicalForm());
            Language lang = Literal.class.cast(resource).getLanguage();
            if (lang != null) {
                element.put("xml:lang", lang.toString());
            }
        } else if (resource instanceof Literal) {
            element.put("type", "typed-literal");
            element.put("datatype", Literal.class.cast(resource).getDataType().getUnicodeString());
            element.put("value", Literal.class.cast(resource).getLexicalForm());
        } else if (resource instanceof BlankNode) {
            element.put("type", "bnode");
            element.put("value", "/");
        } else {
            element = null;
        }
        return element;
    }

    /**
     * Helper: creates results element from ResultSet
     */
    private JSONObject createResult(SolutionMapping solutionMap) {
        JSONObject result = new JSONObject();
        Set<Variable> keys = solutionMap.keySet();
        for (Variable key : keys) {
            result.put(key.getName(), createResultElement((RDFTerm) solutionMap.get(key)));
        }
        return result;
    }

    private void createVariables(List<String> variables, JSONObject head) {
        JSONArray vars = null;
        for (String variable : variables) {
            if (vars == null) {
                vars = new JSONArray();
                head.put("vars", vars);
            }
            vars.add(variable);
        }
    }
}
