package org.etsi.mts.tdl.execution.java.codegen;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

import javax.lang.model.SourceVersion;

import org.etsi.mts.tdl.DataElementMapping;
import org.etsi.mts.tdl.Element;
import org.etsi.mts.tdl.MappableDataElement;
import org.etsi.mts.tdl.Package;

public abstract class Renderer {

	private static final String LF = System.getProperty("line.separator");

	private File generationDir;
	private String rootPackageName;

	protected Map<Element, String> elementNames = new Hashtable<Element, String>();

	private CharBuffer buf;
	private String indent = "";

	public Renderer(File generationDir, String packageName) {
		this.generationDir = generationDir;
		this.rootPackageName = packageName;
	}

	protected String getRootPackageName() {
		return rootPackageName;
	}

	protected void setRootPackageName(String rootPackageName) {
		this.rootPackageName = rootPackageName;
	}

	final void writeClassFile(String packageName, String className, ContentRenderer r) throws IOException {

		File packageDir = this.generationDir;
		for (String p : packageName.split("\\."))
			packageDir = new File(packageDir, p);
		packageDir.mkdirs();

		File classFile = new File(packageDir, className + ".java");

		buf = CharBuffer.allocate(1 * 1024 * 1024);

		r.doRender();

		buf.flip();

		CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
		ByteBuffer bytes;
		try {
			bytes = encoder.encode(buf);
			encoder.flush(bytes);
			bytes.rewind();
		} catch (CharacterCodingException e) {
			throw new RuntimeException(e);
		}

		classFile.createNewFile();
		// Stream gets closed by the channel
		try (FileChannel out = new FileOutputStream(classFile).getChannel()) {
			out.write(bytes);
		}

	}

	protected void writeImports(Set<String> imports) {
		for (String i : imports) {
			line("import " + i + ";");
		}
		newLine();
	}

	protected String getPackageName(Package p) {
		// JDK classes
		if (p.getName().startsWith("java."))
			return p.getName();
		return getElementName(p);
	}

	protected String getElementName(Element e) {
		if (e instanceof MappableDataElement) {
			DataElementMapping mapping = getMapping((MappableDataElement) e);
			if (mapping != null)
				return mapping.getElementURI();

		}
		String name = this.elementNames.get(e);
		if (name != null)
			return name;

		name = e.getName();
		if (name == null)
			name = e.eClass().getName();
		String identifier = getIdentifier(name);

		int i = 1;
		name = identifier;
		while (this.elementNames.containsValue(name))
			name = identifier + "_" + i++;
		this.elementNames.put(e, name);

		return name;
	}

	protected abstract DataElementMapping getMapping(MappableDataElement e);

	protected void append(String text) {
		buf.append(text);
	}

	protected void blockOpen() {
		append(" {");
		indent += "\t";
		newLine();
	}

	protected void blockClose() {
		indent = indent.substring(1);
		// Rewind a tab
		buf.position(buf.position() - 1);
		line("}");
	}

	protected void blockCloseArrow() {
		indent = indent.substring(1);
		// Rewind a tab
		buf.position(buf.position() - 1);
		line("});");
	}

	protected void blockCloseMethod() {
		indent = indent.substring(1);
		// Rewind a tab
		buf.position(buf.position() - 1);
		line("};");
	}

	protected void blockOpenParen() {
		append(" (");
		indent += "\t";
		newLine();
	}

	protected void blockCloseParen() {
		indent = indent.substring(1);
		// Rewind a tab
		buf.position(buf.position() - 1);
		line(")");
	}

	protected void callOpen() {
		append("(");
		indent += "\t\t";
		newLine();
	}

	protected void callClose() {
		indent = indent.substring(2);
		line(");");
	}

	protected void newLine() {
		append(LF);
		append(indent);
	}

	protected void line(String text) {
		append(text);
		newLine();
	}

	protected void lineInc(String text) {
		append("\t");
		append(text);
		newLine();
	}

	protected void lineComment(String text) {
		line("// " + text);
	}

	private String getIdentifier(String name) {
		if (name == null)
			return null;
		if (SourceVersion.isKeyword(name))
			name = name + "_";
		char[] chars = name.toCharArray();
		for (int i = 0; i < chars.length; i++) {
			if (i == 0) {
				if (!Character.isJavaIdentifierStart(chars[i]))
					chars[i] = '_';
			} else if (!Character.isJavaIdentifierPart(chars[i]))
				chars[i] = '_';
		}
		return String.valueOf(chars);
	}

	@FunctionalInterface
	interface ContentRenderer {
		void doRender();
	}
	
}
