package de.fuberlin.wiwiss.d2rs;

import java.io.IOException;
import java.io.OutputStreamWriter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.RDFWriter;
import com.hp.hpl.jena.shared.JenaException;

import de.fuberlin.wiwiss.pubby.negotiation.ContentTypeNegotiator;
import de.fuberlin.wiwiss.pubby.negotiation.MediaRangeSpec;
import de.fuberlin.wiwiss.pubby.negotiation.PubbyNegotiator;

/**
 * Calls into Joseki to send a Jena model over HTTP. This gives us
 * content negotiation and all the other tricks supported by Joseki
 * for free. This has to be in the Joseki package because some
 * required methods are not visible.
 * 
 * @author Richard Cyganiak (richard@cyganiak.de)
 * @version $Id: ModelResponse.java,v 1.1 2008/09/01 11:35:07 cyganiak Exp $
 */
public class ModelResponse {
	private final Model model;
	private final HttpServletRequest request;
	private final HttpServletResponse response;
	
	public ModelResponse(Model model, HttpServletRequest request, 
			HttpServletResponse response) {

		// Handle ?output=format request parameter
		RequestParamHandler handler = new RequestParamHandler(request);
		if (handler.isMatchingRequest()) {
			request = handler.getModifiedRequest();
		}
		
		this.model = model;
		this.request = request;
		this.response = response;
	}
	
	public void serve() {
		// Error handling is still quite a mess here.
		try {
			doResponseModel();
		} catch (IOException ioEx) {
			throw new RuntimeException(ioEx);
		} catch (JenaException jEx) {
			try {
				response.sendError(
						HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
						"JenaException: " + jEx.getMessage());
            } catch (IOException e) {
            	throw new RuntimeException(e);
            }
		}
	}
	
	private void doResponseModel() throws IOException {
		response.addHeader("Vary", "Accept");
		ContentTypeNegotiator negotiator = PubbyNegotiator.getDataNegotiator();
		MediaRangeSpec bestMatch = negotiator.getBestMatch(
				request.getHeader("Accept"), request.getHeader("User-Agent"));
		if (bestMatch == null) {
			response.setStatus(406);
			response.setContentType("text/plain");
			ServletOutputStream out = response.getOutputStream();
			out.println("406 Not Acceptable: The requested data format is not supported.");
			out.println("Supported formats are RDF/XML, Turtle, N3, and N-Triples.");
			return;
		}
		response.setContentType(bestMatch.getMediaType());
		getWriter(bestMatch.getMediaType()).write(model, response);
		response.getOutputStream().flush();
    }
	
	private ModelWriter getWriter(String mediaType) {
		if ("application/rdf+xml".equals(mediaType)) {
			return new RDFXMLWriter();
		}
		if ("application/x-turtle".equals(mediaType)) {
			return new N3Writer();
		}
		if ("text/rdf+n3;charset=utf-8".equals(mediaType)) {
			return new N3Writer();
		}
		return new NTriplesWriter();
	}
	
	private interface ModelWriter {
		void write(Model model, HttpServletResponse response) throws IOException;
	}
	
	private class NTriplesWriter implements ModelWriter {
		public void write(Model model, HttpServletResponse response) throws IOException {
			model.getWriter("N-TRIPLES").write(model, response.getOutputStream(), null);
		}
	}
	
	private class N3Writer implements ModelWriter {
		public void write(Model model, HttpServletResponse response) throws IOException {
			model.getWriter("N3").write(model, response.getOutputStream(), null);
		}
	}
	
	private class RDFXMLWriter implements ModelWriter {
		public void write(Model model, HttpServletResponse response) throws IOException {
			RDFWriter writer = model.getWriter("RDF/XML-ABBREV");
			writer.setProperty("showXmlDeclaration", "true");
			// From Joseki -- workaround for the j.cook.up bug.
			writer.setProperty("blockRules", "propertyAttr");
			writer.write(model, 
					new OutputStreamWriter(response.getOutputStream(), "utf-8"), null);
		}
	}
}
