/*******************************************************************************
 * Copyright (c) 2015, 2018 Open Analytics NV and others.
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/
package org.eclipse.linuxtools.internal.docker.editor;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnmappableCharacterException;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.rules.FastPartitioner;
import org.eclipse.linuxtools.internal.docker.editor.scanner.DockerPartitionScanner;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.FileDocumentProvider;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel;

public class DockerDocumentProvider extends FileDocumentProvider {

	@Override
	protected IDocument createDocument(Object element) throws CoreException {
		IDocument document = super.createDocument(element);
		if (document != null) {
			DockerPartitionScanner scanner = new DockerPartitionScanner();
			IDocumentPartitioner partitioner = new FastPartitioner(scanner,
					DockerPartitionScanner.ALLOWED_CONTENT_TYPES);
			partitioner.connect(document);
			document.setDocumentPartitioner(partitioner);
		}
		return document;
	}

	@Override
	public String getEncoding(Object element) {
		return Charset.defaultCharset().name();
	}

	@Override
	public boolean isReadOnly(Object element) {
		if (element instanceof FileStoreEditorInput) {
			String path = ((FileStoreEditorInput) element).getURI().getPath();
			return !new File(path).canWrite();
		} else {
			return super.isReadOnly(element);
		}
	}

	@Override
	public boolean isModifiable(Object element) {
		if (element instanceof FileStoreEditorInput) {
			String path = ((FileStoreEditorInput) element).getURI().getPath();
			return new File(path).canWrite();
		} else {
			return super.isModifiable(element);
		}
	}

	@Override
	protected boolean setDocumentContent(IDocument document, IEditorInput editorInput, String encoding)
			throws CoreException {
		if (editorInput instanceof FileStoreEditorInput) {
			String path = ((FileStoreEditorInput) editorInput).getURI().getPath();
			InputStream in;
			try {
				in = new FileInputStream(path);
			} catch (FileNotFoundException e) {
				return false;
			}
			super.setDocumentContent(document, in, encoding);
			return true;
		} else {
			return super.setDocumentContent(document, editorInput, encoding);
		}
	}

	@Override
	protected void doSaveDocument(IProgressMonitor monitor, Object element, IDocument document, boolean overwrite)
			throws CoreException {
		if (element instanceof FileStoreEditorInput) {

			String encoding = null;

			ElementInfo info = getElementInfo(element);
			Path filePath = Paths.get(((FileStoreEditorInput) element).getURI());

			encoding = getEncoding(element);

			Charset charset;
			try {
				charset = Charset.forName(encoding);
			} catch (UnsupportedCharsetException ex) {
				String message = NLS.bind(Messages.DockerDocumentProvider_encoding_not_supported, encoding);
				IStatus s = new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, message, ex);
				throw new CoreException(s);
			} catch (IllegalCharsetNameException ex) {
				String message = NLS.bind(Messages.DockerDocumentProvider_encoding_not_legal, encoding);
				IStatus s = new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, message, ex);
				throw new CoreException(s);
			}

			CharsetEncoder encoder = charset.newEncoder();
			encoder.onMalformedInput(CodingErrorAction.REPLACE);
			encoder.onUnmappableCharacter(CodingErrorAction.REPORT);

			InputStream stream;

			try {
				byte[] bytes;
				ByteBuffer byteBuffer = encoder.encode(CharBuffer.wrap(document.get()));
				if (byteBuffer.hasArray())
					bytes = byteBuffer.array();
				else {
					bytes = new byte[byteBuffer.limit()];
					byteBuffer.get(bytes);
				}
				stream = new ByteArrayInputStream(bytes, 0, byteBuffer.limit());
			} catch (CharacterCodingException ex) {
				Assert.isTrue(ex instanceof UnmappableCharacterException);
				String message = NLS.bind(Messages.DockerDocumentProvider_cannot_be_mapped
						+ Messages.DockerDocumentProvider_chars_not_supported, encoding);
				IStatus s = new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, EditorsUI.CHARSET_MAPPING_FAILED, message,
						null);
				throw new CoreException(s);
			}

			if (Files.exists(filePath)) {
				// inform about the upcoming content change
				fireElementStateChanging(element);
				try (FileWriter fw = new FileWriter(filePath.toFile());
						InputStreamReader istream = new InputStreamReader(stream)) {
					char[] bb = new char[1024];
					int nRead = istream.read(bb);
					while (nRead > 0) {
						fw.write(bb, 0, nRead);
						nRead = istream.read(bb);
					}
				} catch (RuntimeException | IOException x) {
					// inform about failure
					fireElementStateChangeFailed(element);
					throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, x.getMessage()));
				}

				// If here, the editor state will be flipped to "not dirty".
				// Thus, the state changing flag will be reset.

				if (info != null) {

					ResourceMarkerAnnotationModel model = (ResourceMarkerAnnotationModel) info.fModel;
					if (model != null)
						model.updateMarkers(info.fDocument);
				}

			} else {
				try {
					Files.createFile(filePath);
					try (FileWriter fw = new FileWriter(filePath.toFile());
							InputStreamReader istream = new InputStreamReader(stream)) {
						char[] bb = new char[1024];
						int nRead = istream.read(bb);
						while (nRead > 0) {
							fw.write(bb, 0, nRead);
							nRead = istream.read(bb);
						}
					} catch (IOException x) {
						throw x;
					}
				} catch (IOException e) {
					throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage()));
				} finally {
					monitor.done();
				}
			}

		} else {
			super.doSaveDocument(monitor, element, document, overwrite);
		}
	}

}