/**
 * Copyright © 2017 by Data Virtuality GmbH
 */
package org.teiid.translator.jdbc;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
import org.teiid.core.types.ArrayImpl;
import org.teiid.core.types.BinaryType;
import org.teiid.core.util.Base64;
import org.teiid.language.ColumnReference;
import org.teiid.language.Command;
import org.teiid.language.Insert;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.UpdateExecution;
import org.teiid.translator.ExecutionFactory.ArrayEmulation;
import org.teiid.translator.jdbc.plugin.JDBCPlugin;

/**
 * @author Roman Pedchenko
 * @date 2017.06.01
 */
public interface MultilInsertExecutionFactoryIntf {

	public enum UploadMode {
		STANDARD, BULKINSERTSINGLETHREAD, BULKINSERTMULTITHREAD;

		public static boolean multiThreaded(String uploadMode) {
			return mode(uploadMode) == BULKINSERTMULTITHREAD;
		}

		public static boolean bulk(String uploadMode) {
			return mode(uploadMode) != STANDARD;
		}

		private static UploadMode mode(String uploadMode) {
			try {
				return UploadMode.valueOf(uploadMode.toUpperCase());
			} catch (IllegalArgumentException e) {
				// this shouldn't occur because we always check mode on translator start
				return UploadMode.STANDARD;
			}
		}
	}

	public String uploadMode();
	public Integer maxChunkSize();
	public Integer maxRowCount();
	public Integer numberOfThreads();
	public ArrayEmulation supportsArrayEmulation();
	public String valueToString(ColumnReference col, Object value) throws TranslatorException;
	public String quoteValue(String value);
	public String quoteBinary(String value);

	public class Util {
		public static void setupUploadMode(MultilInsertExecutionFactoryIntf ef) throws TranslatorException {
			if (ef.uploadMode() != null)
				try {
					UploadMode.valueOf(ef.uploadMode().toUpperCase());
				} catch (IllegalArgumentException e) {
					String values = "";
					for (int i = 0; i < UploadMode.values().length; i++) {
						if (i > 0)
							values += ", ";
						values += UploadMode.values()[i];
					}
					throw new TranslatorException(JDBCPlugin.Util.gs(JDBCPlugin.Event.DVCORE94099, ef.uploadMode(), values));
				}
		}

		public static UpdateExecution createUpdateExecution(MultilInsertExecutionFactoryIntf ef, Command command, ExecutionContext executionContext, RuntimeMetadata metadata, Connection connection) throws TranslatorException {
			if (command instanceof Insert && MultilInsertExecutionFactoryIntf.UploadMode.bulk(ef.uploadMode()))
				return new MultilInsertExecutionImpl((Insert) command, connection, executionContext, ef);
			return null;
		}

		public static String defaultValueToString(MultilInsertExecutionFactoryIntf ef, ColumnReference col, Object value) throws TranslatorException {
			if (value instanceof Clob) {
				final Clob clob = (Clob) value;
				try {
					int len = (int) clob.length();
					return ef.quoteValue(clob.getSubString(1, len));
				} catch (SQLException e) {
					throw new TranslatorException(e);
				}
			}
			if (value instanceof Blob) {
				final Blob blob = (Blob) value;
				try {
					InputStream bs = blob.getBinaryStream();
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					try {
						IOUtils.copy(bs, baos);
					} catch (IOException e) {
						throw new TranslatorException(e);
					}
					return ef.quoteBinary(new String(Hex.encodeHex(baos.toByteArray())));
				} catch (SQLException e) {
					throw new TranslatorException(e);
				}
			}
			if (value instanceof BinaryType) {
				final BinaryType blob = (BinaryType) value;
				return ef.quoteBinary(new String(Hex.encodeHex(blob.getBytes())));
			}
			if (ef.supportsArrayEmulation() == ArrayEmulation.STRING) {
				if (value instanceof ArrayImpl)
					try {
						ByteArrayOutputStream baos = new ByteArrayOutputStream();
						ObjectOutputStream out = null;
						try {
							out = new ObjectOutputStream(baos);
							((ArrayImpl) value).writeExternal(out);
							out.flush();
							byte[] bytes = baos.toByteArray();
							return ef.quoteValue(Base64.encodeBytes(bytes));
						} finally {
							if (out != null)
								out.close();
						}
					} catch (IOException e) {
						throw new TranslatorException(e);
					}
			}
			if (value instanceof Boolean) {
				return value.toString();
			}
			return ef.quoteValue(value.toString());
		}
	}
}
