/*
 * Copyright, 2010 Koichi Takahashi. All Rights Reserved.
 * 
 * Licensed 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.
 */
package com.nec.exi.editor;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Properties;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.editors.text.TextEditor;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import com.siemens.ct.exi.CodingMode;
import com.siemens.ct.exi.EXIFactory;
import com.siemens.ct.exi.GrammarFactory;
import com.siemens.ct.exi.api.sax.EXIResult;
import com.siemens.ct.exi.api.sax.EXISource;
import com.siemens.ct.exi.helpers.DefaultEXIFactory;

/**
 * @author Koichi Takahashi
 */
public class EXIEditor extends TextEditor {

	private ILog logger = Activator.getDefault().getLog();
	private ColorManager colorManager;
	private Transformer transformer;
	private IEditorInput input = null;
	private IFile iFile = null;
	private File ioFile = null;
	private String encoding = "UTF-8"; //$NON-NLS-1$
	private EXIFactory exiFactory = null;
	private CodingMode save_mode = null;
	private byte[] backup;
	private boolean saved = false;
	private Properties property = null;
	private String _fileName = null;

	public EXIEditor() {
		super();
		colorManager = new ColorManager();
		setSourceViewerConfiguration(new XMLConfiguration(colorManager));
		setDocumentProvider(new XMLDocumentProvider());
		property = new Properties();
		
		try {
			transformer = TransformerFactory.newInstance().newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
			exiFactory = DefaultEXIFactory.newInstance();
		} catch (Throwable e) {
			IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, Resources.get("EXIEditor.1"), e); //$NON-NLS-1$
			ErrorDialog.openError(getSite().getWorkbenchWindow().getShell(), null, null, status);
			logger.log(status);
		}
	}
	
	public void dispose() {
		colorManager.dispose();
		super.dispose();
		if(!isDirty() && !saved){
			flush();
		}
	}
	
	public void createPartControl(Composite parent) {
		super.createPartControl(parent);
		input = getEditorInput();
   		String fileName = null;
   		boolean isReadOnly = false;
   		FileInputStream _exi = null;
   		ByteArrayInputStream exi = null;
   		ByteArrayOutputStream xml = null;
		File dir = null;
		File propertyFile = null;
		String xsd = null;
		
		try {			
			if (input instanceof IFileEditorInput) {
				iFile = ((IFileEditorInput) input).getFile();
				ioFile = iFile.getRawLocation().toFile();
				fileName = iFile.getName();
				isReadOnly = iFile.isReadOnly();
			} else if (input instanceof IPathEditorInput) {
				IPathEditorInput pathEditorInput = (IPathEditorInput) input;
				IPath path = pathEditorInput.getPath();
				ioFile = path.toFile();
				fileName = ioFile.getName();
				isReadOnly = !ioFile.canWrite();
			} else {
				IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, Resources.get("EXIEditor.2")); //$NON-NLS-1$
				logger.log(status);
				throw new Exception(Resources.get("EXIEditor.2")); //$NON-NLS-1$
			}
			
			if (null != fileName) {
				setPartName(fileName + ((isReadOnly) ? "[Read-Only]": "")); //$NON-NLS-1$ //$NON-NLS-2$
			}
			
			dir = ioFile.getParentFile();
			propertyFile = new File(dir.getAbsolutePath() + File.separator + ".schema"); //$NON-NLS-1$
			for(String propertyFileName : dir.list()){
				if(".schema".equals(propertyFileName)){ //$NON-NLS-1$
					property.loadFromXML(new FileInputStream(propertyFile));
					xsd = property.getProperty(ioFile.getName());
					if(null != xsd){
						File xsdFile = new File(xsd);
						if(xsdFile.exists()){
							xsd = xsdFile.toURI().toString();
							break;
						}else{
							throw new Exception(Resources.get("EXIEditor.3")); //$NON-NLS-1$
						}
					}
				}
			}
			
   			_exi = new FileInputStream(ioFile);
   			backup = new Util().inputStreamToByteArray(_exi);
   			exi = new ByteArrayInputStream(backup);
   			xml = new ByteArrayOutputStream();
        	String[][] codingModes = Activator.CODING_MODE_NV;
        	String codingMode = null;
			_fileName = ioFile.getName();
			_fileName = _fileName.substring(0, _fileName.length()-4);
            for(int i=1; i<5; i++){
            	codingMode = codingModes[i][1];
            	save_mode = CodingMode.valueOf(codingMode);
            	try {
					decode(exi, xml, save_mode, xsd);
					String[] args = new String[]{ioFile.getName(), codingMode, _fileName};
					if(null != xsd){
						MessageFormat format = new MessageFormat(Resources.get("EXIEditor.4")); //$NON-NLS-1$
						IStatus status = new Status(IStatus.INFO, Activator.PLUGIN_ID, format.format(args));
						logger.log(status);
					}else{
						MessageFormat format = new MessageFormat(Resources.get("EXIEditor.5")); //$NON-NLS-1$
						IStatus status = new Status(IStatus.INFO, Activator.PLUGIN_ID, format.format(args));
						logger.log(status);						
					}
					break;
				} catch (Exception e) {
	            	if(i==4){
	            		save_mode = null;
	            		throw new Exception(Resources.get("EXIEditor.6")); //$NON-NLS-1$
	            	}else{
		            	exi.reset();
		            	xml.reset();
	            	}
				}
            }
            
			IDocument document = this.getDocumentProvider().getDocument(input);
			document.set(xml.toString(encoding));
			// 変更したという「*」を消すために一旦XMLを保存
			super.performSave(true, getProgressMonitor()); // trueがポイント
			// バックアップしておいたEXIを保存し、元に戻す。
			flush();
		} catch (Exception e) {
			IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, Resources.get("EXIEditor.7"), e); //$NON-NLS-1$
			ErrorDialog.openError(getSite().getWorkbenchWindow().getShell(), null, null, status);
			logger.log(status);
		} finally {
			try {
				if(null != exi) exi.close();
				if(null != xml) xml.close();
			} catch (Exception e) {
				IStatus status = new Status(IStatus.WARNING, Activator.PLUGIN_ID, Resources.get("EXIEditor.8"), e); //$NON-NLS-1$
				logger.log(status);
			}			
		}
	}
	
	public boolean isSaveAsAllowed() {
		return false; // 別名で保存は許可しない
	}
	
	public void doSave(IProgressMonitor progressMonitor) {
		// 変更したという「*」を消すために一旦XMLを保存
		super.performSave(true, progressMonitor); // trueがポイント
		// エディターのXMLをEXIにエンコードして保存
		save();
	}
	
	private void save() {
		IDocument document = this.getDocumentProvider().getDocument(input);
		BufferedInputStream xml = null;
		ByteArrayOutputStream _exi = null;
		FileOutputStream exi = null;
		IPreferenceStore store = Activator.getDefault().getPreferenceStore();
		File dir = ioFile.getParentFile();
		File propertyFile = new File(dir.getAbsolutePath() + File.separator + ".schema"); //$NON-NLS-1$
		String xsd = null;
		
		try {
			for(String propertyFileName : dir.list()){
				if(".schema".equals(propertyFileName)){ //$NON-NLS-1$
					property.loadFromXML(new FileInputStream(propertyFile));
					xsd = property.getProperty(ioFile.getName());
					break;
				}
			}
			xml = new BufferedInputStream(new ByteArrayInputStream(document.get().getBytes(encoding)));
			_exi = new ByteArrayOutputStream();
			String codingModeValue = store.getString(Activator.CODING_MODE_N);
			if("" != codingModeValue){ //$NON-NLS-1$
				save_mode = CodingMode.valueOf(codingModeValue);
			}
			if(null != save_mode && !"".equals(save_mode)){ //$NON-NLS-1$
				encode(xml, _exi, save_mode, xsd);
			}else{
				throw new Exception(Resources.get("EXIEditor.9")); //$NON-NLS-1$
			}
			exi = new FileOutputStream(ioFile);
			_exi.writeTo(exi);
			String[] args = new String[]{ioFile.getName(), save_mode.toString(), _fileName};
			if(null != xsd){
				MessageFormat format = new MessageFormat(Resources.get("EXIEditor.10")); //$NON-NLS-1$
				IStatus status = new Status(IStatus.INFO, Activator.PLUGIN_ID, format.format(args));
				logger.log(status);
			}else{
				MessageFormat format = new MessageFormat(Resources.get("EXIEditor.11")); //$NON-NLS-1$
				IStatus status = new Status(IStatus.INFO, Activator.PLUGIN_ID, format.format(args));
				logger.log(status);					
			}
			saved = true;
		} catch (Exception e) {
			IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, Resources.get("EXIEditor.12"), e); //$NON-NLS-1$
			ErrorDialog.openError(getSite().getWorkbenchWindow().getShell(), null, null, status);
			logger.log(status);
		} finally {
			try {
				if(null != _exi) _exi.close();
				if(null != exi) exi.close();
				if(null != xml) xml.close();
			} catch (Exception e) {
				IStatus status = new Status(IStatus.WARNING, Activator.PLUGIN_ID, Resources.get("EXIEditor.8"), e); //$NON-NLS-1$
				logger.log(status);
			}			
		}
	}
	
	private void flush() {
		FileOutputStream output = null;
		ByteArrayInputStream input = new ByteArrayInputStream(backup);
		
		try {
			output = new FileOutputStream(ioFile);
			new Util().inputStreamToOutputStream(input, output);
		} catch (Exception e) {
			IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, Resources.get("EXIEditor.13"), e); //$NON-NLS-1$
			ErrorDialog.openError(getSite().getWorkbenchWindow().getShell(), null, null, status);
			logger.log(status);
		} finally {
			try {
				if(null != output) output.close();
			} catch (Exception e) {
				IStatus status = new Status(IStatus.WARNING, Activator.PLUGIN_ID, Resources.get("EXIEditor.8"), e); //$NON-NLS-1$
				logger.log(status);
			}			
		}
	}
	
	private void encode(InputStream xml, OutputStream exi, CodingMode mode, String xsd) throws Exception {
		exiFactory.setCodingMode(mode);
		if(null != xsd){
			exiFactory.setGrammar(GrammarFactory.newInstance().createGrammar(xsd));        		
		}
		SAXResult exiResult = new EXIResult(exi, exiFactory);
		XMLReader xmlReader = XMLReaderFactory.createXMLReader();
		xmlReader.setContentHandler(exiResult.getHandler());
		xmlReader.parse(new InputSource(xml));
		//transformer.transform(new StreamSource(xml), exiResult); // The ban on use !
	}
	
	private void decode(InputStream exi, OutputStream xml, CodingMode mode, String xsd) throws Exception {
		exiFactory.setCodingMode(mode);
		if(null != xsd){
			exiFactory.setGrammar(GrammarFactory.newInstance().createGrammar(xsd));        		
		}
		EXISource exiSource = new EXISource(exiFactory);
		exiSource.setInputSource(new InputSource(exi));
		transformer.transform(exiSource, new StreamResult(xml));
	}

}
