package org.apache.taverna.tavlang.tools.convert;

/*
 * 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.
 */

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.taverna.scufl2.api.annotation.Annotation;
import org.apache.taverna.scufl2.api.annotation.Revision;
import org.apache.taverna.scufl2.api.common.Child;
import org.apache.taverna.scufl2.api.common.Ported;
import org.apache.taverna.scufl2.api.common.Scufl2Tools;
import org.apache.taverna.scufl2.api.common.URITools;
import org.apache.taverna.scufl2.api.container.WorkflowBundle;
import org.apache.taverna.scufl2.api.core.BlockingControlLink;
import org.apache.taverna.scufl2.api.core.ControlLink;
import org.apache.taverna.scufl2.api.core.DataLink;
import org.apache.taverna.scufl2.api.core.Processor;
import org.apache.taverna.scufl2.api.core.Workflow;
import org.apache.taverna.scufl2.api.io.ReaderException;
import org.apache.taverna.scufl2.api.io.WorkflowBundleIO;
import org.apache.taverna.scufl2.api.io.WorkflowBundleWriter;
import org.apache.taverna.scufl2.api.io.WriterException;
import org.apache.taverna.scufl2.api.port.DepthPort;
import org.apache.taverna.scufl2.api.port.GranularDepthPort;
import org.apache.taverna.scufl2.api.port.Port;
import org.apache.taverna.scufl2.api.profiles.Profile;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;

public class ToJson {
	public class JsonWriter implements WorkflowBundleWriter {

		@Override
		public Set<String> getMediaTypes() {
			return new HashSet<String>(Arrays.asList("application/ld+json",
					"application/json"));
		}

		@Override
		public void writeBundle(WorkflowBundle wfBundle, File destination,
				String mediaType) throws WriterException, IOException {
			ObjectNode json = toJson(wfBundle);
			mapper.writeValue(destination, json);
		}

		@Override
		public void writeBundle(WorkflowBundle wfBundle, OutputStream output,
				String mediaType) throws WriterException, IOException {
			ObjectNode json = toJson(wfBundle);
			mapper.writeValue(output, json);
		}

	}

	
	private WorkflowBundleIO io = new WorkflowBundleIO();;

	private WorkflowBundleWriter jsonWriter = new JsonWriter();

	private ObjectMapper mapper = new ObjectMapper();

	private Scufl2Tools scufl2Tools = new Scufl2Tools();

	private URITools uriTools = new URITools();

	public ToJson() {
		mapper.enable(SerializationFeature.INDENT_OUTPUT);
		mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
		mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

		mapper.setDateFormat(new ISO8601DateFormat());

		// Adding custom writer dynamically
		List<WorkflowBundleWriter> writers = io.getWriters();
		writers.add(jsonWriter);
		io.setWriters(writers);
	}

	protected void addPorts(Ported ported, ObjectNode p) {
		ArrayNode inputs = mapper.createArrayNode();
		for (Port port : ported.getInputPorts()) {
			inputs.add(toJson(port));
		}
		p.put("inputs", inputs);

		ArrayNode outputs = mapper.createArrayNode();
		for (Port port : ported.getOutputPorts()) {
			outputs.add(toJson(port));
			// FIXME: Do we need the id for ports? Needed if we add datalinks
		}
		p.put("outputs", outputs);
	}

	protected ObjectNode annotations(Child<?> bean) {
		ObjectNode node = mapper.createObjectNode();
		for (Annotation ann : scufl2Tools.annotationsFor(bean)) {
			URI annUri = uriTools.uriForBean(ann);

			// TODO: include annotation body?
		}
		return node;
	}

	public void convert(File in, File out) throws ReaderException, IOException,
			WriterException {
		String filename = in.getName();
		filename = filename.replaceFirst("\\..*", ".json");
		File workflowFile = new File(out.getAbsolutePath(), filename);

		WorkflowBundle wfBundle = io.readBundle(in, null);
		io.writeBundle(wfBundle, workflowFile, "application/ld+json");
//		System.out.println(workflowFile);

	}

	protected ObjectNode toJson(Port port) {
		ObjectNode p = mapper.createObjectNode();
		p.put("name", port.getName());
		p.putPOJO("id",
				uriTools.relativeUriForBean(port, scufl2Tools.findParent(
						WorkflowBundle.class, ((Child<?>) port))));

		if (port instanceof DepthPort) {
			DepthPort depthPort = (DepthPort) port;
			if (depthPort.getDepth() != null) {
				p.put("depth", depthPort.getDepth());
			}
		}
		if (port instanceof GranularDepthPort) {
			GranularDepthPort granularDepthPort = (GranularDepthPort) port;
			if (granularDepthPort.getGranularDepth() != null
					&& !granularDepthPort.getGranularDepth().equals(
							granularDepthPort.getDepth())) {
				p.put("granularDepth", granularDepthPort.getGranularDepth());
			}
		}
		p.putAll(annotations((Child<?>) port));
		return p;
	}

	protected JsonNode toJson(Processor proc) {
		ObjectNode p = mapper.createObjectNode();
		p.putPOJO("id",
				uriTools.relativeUriForBean(proc, proc.getParent().getParent()));
		p.put("name", proc.getName());
		addPorts(proc, p);
		p.putAll(annotations(proc));

		List<Workflow> nested = scufl2Tools.nestedWorkflowsForProcessor(proc,
				proc.getParent().getParent().getMainProfile());
		if (!nested.isEmpty()) {
			if (nested.size() == 1) {
				p.put("nestedWorkflow", toJson(nested.iterator().next()));
			} else {
				ArrayNode list = mapper.createArrayNode();
				for (Workflow w : nested) {
					list.add(toJson(w));
				}
				p.put("nestedWorkflow", list);
			}
		}
		return p;
	}

	protected JsonNode toJson(Revision currentRevision) {
		ArrayNode revisions = mapper.createArrayNode();
		while (currentRevision != null) {
			ObjectNode rev = mapper.createObjectNode();
			rev.putPOJO("id", currentRevision.getIdentifier());
			if (currentRevision.getGeneratedAtTime() != null) {
				rev.putPOJO("generatedAtTime",
						currentRevision.getGeneratedAtTime());
			}
			currentRevision = currentRevision.getPreviousRevision();
			if (currentRevision != null) {
				rev.putPOJO("wasRevisionOf", currentRevision.getIdentifier());
			}
			revisions.add(rev);
		}
		return revisions;
	}

	protected ObjectNode toJson(Workflow workflow) {
		ObjectNode wf = mapper.createObjectNode();

		wf.putPOJO("id",
				uriTools.relativeUriForBean(workflow, workflow.getParent()));

		wf.put("name", workflow.getName());
		wf.put("revisions", toJson(workflow.getCurrentRevision()));

		ArrayNode processors = mapper.createArrayNode();
		for (Processor p : workflow.getProcessors()) {
			processors.add(toJson(p));
		}
		addPorts(workflow, wf);
		wf.put("processors", processors);

		ArrayNode datalinks = mapper.createArrayNode();
		for (DataLink link : workflow.getDataLinks()) {
			datalinks.add(toJson(link));
		}
		wf.put("datalinks", datalinks);

		ArrayNode controlLinks = mapper.createArrayNode();
		for (ControlLink link : workflow.getControlLinks()) {
			controlLinks.add(toJson(link));
		}
		wf.put("controllinks", controlLinks);

		wf.putAll(annotations(workflow));

		return wf;
	}

	protected JsonNode toJson(ControlLink link) {
		ObjectNode l = mapper.createObjectNode();
		if (link instanceof BlockingControlLink) {
			BlockingControlLink controlLink = (BlockingControlLink) link;
			l.putPOJO("block", uriTools.relativeUriForBean(
					controlLink.getBlock(), link.getParent().getParent()));
			l.putPOJO("untilFinished", uriTools.relativeUriForBean(controlLink
					.getUntilFinished(), link.getParent().getParent()));
		}
		return l;
	}

	protected JsonNode toJson(DataLink link) {
		ObjectNode l = mapper.createObjectNode();
		l.putPOJO("receivesFrom", uriTools.relativeUriForBean(
				link.getReceivesFrom(), link.getParent().getParent()));
		l.putPOJO("sendsTo", uriTools.relativeUriForBean(link.getSendsTo(),
				link.getParent().getParent()));
		if (link.getMergePosition() != null) {
			l.put("mergePosition", link.getMergePosition());
		}
		return l;
	}

	public ObjectNode toJson(WorkflowBundle wfBundle) {

		ObjectNode root = mapper.createObjectNode();
		ArrayNode contextList = root.arrayNode();
		root.put("@context", contextList);
		ObjectNode context = root.objectNode();
		contextList.add("https://w3id.org/scufl2/context");
		contextList.add(context);
		URI base = wfBundle.getGlobalBaseURI();
		context.put("@base", base.toASCIIString());
		root.put("id", base.toASCIIString());

		// root.put("name", wfBundle.getName());
		// root.put("revisions", toJson(wfBundle.getCurrentRevision()));

		root.put("workflow", toJson(wfBundle.getMainWorkflow()));
		root.put("profile", toJson(wfBundle.getMainProfile()));

		return root;
	}

	private JsonNode toJson(Profile profile) {
		ObjectNode pf = mapper.createObjectNode();

		pf.putPOJO("id",
				uriTools.relativeUriForBean(profile, profile.getParent()));
		// TODO: Activities and configurations
		return pf;
	}
}