// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.xml.common;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.openstreetmap.osmosis.core.OsmosisRuntimeException;


/**
 * An OSM data sink for storing all data to an xml file.
 * 
 * @author Brett Henderson
 */
public abstract class BaseXmlWriter {
	
	private static Logger log = Logger.getLogger(BaseXmlWriter.class.getName());
	
	
	private boolean closeRequired;
	private boolean writerProvided;
	private File file;
	private boolean initialized;
	private BufferedWriter writer;
	private CompressionMethod compressionMethod;
	
	
	/**
	 * Creates a new instance to write to the provided writer.
	 * 
	 * @param writer The writer to receive data.  This writer will not be closed on completion.
	 */
	public BaseXmlWriter(BufferedWriter writer) {
		this.writer = writer;
		
		writerProvided = true;
		closeRequired = false;
	}
	
	
	/**
	 * Creates a new instance to write to the specified file.
	 * 
	 * @param file
	 *            The file to write.
	 * @param compressionMethod
	 *            Specifies the compression method to employ.
	 */
	public BaseXmlWriter(File file, CompressionMethod compressionMethod) {
		this.file = file;
		this.compressionMethod = compressionMethod;
		
		writerProvided = false;
		closeRequired = true;
	}
	
	
	/**
	 * Sets the writer on the element writer used for this implementation.
	 * 
	 * @param resultWriter
	 *            The writer receiving xml data.
	 */
	protected abstract void setWriterOnElementWriter(BufferedWriter resultWriter);
	
	
	/**
	 * Calls the begin method of the element writer used for this implementation.
	 */
	protected abstract void beginElementWriter();
	
	
	/**
	 * Calls the end method of the element writer used for this implementation.
	 */
	protected abstract void endElementWriter();
	
	
	/**
	 * Writes data to the output file.
	 * 
	 * @param data
	 *            The data to be written.
	 */
	private void write(String data) {
		try {
			writer.write(data);
			
		} catch (IOException e) {
			throw new OsmosisRuntimeException("Unable to write data.", e);
		}
	}
	
	
	/**
	 * Writes a new line in the output file.
	 */
	private void writeNewLine() {
		try {
			writer.newLine();
			
		} catch (IOException e) {
			throw new OsmosisRuntimeException("Unable to write data.", e);
		}
	}
    
    
	/**
	 * Initialize the object.
	 * 
	 * @param metaData
	 *            Meta data applicable to this pipeline invocation.
	 */
	public void initialize(Map<String, Object> metaData) {
		// Do nothing.
	}
	
	
	/**
	 * Initialises the output file for writing. This must be called by
	 * sub-classes before any writing is performed. This method may be called
	 * multiple times without adverse affect allowing sub-classes to invoke it
	 * every time they perform processing.
	 */
	protected void initialize() {
		if (!initialized) {
			if (!writerProvided) {
				OutputStream outStream = null;
				
				try {
					OutputStreamWriter outStreamWriter;
					
					// make "-" an alias for /dev/stdout
					if (file.getName().equals("-")) {
						outStream = System.out;
						
						// We don't want to close stdout because we'll need to
						// re-use it if we receive multiple streams.
						closeRequired = false;
					} else {
						outStream = new FileOutputStream(file);
					}
					
					outStream =
						new CompressionActivator(compressionMethod).createCompressionOutputStream(outStream);
					
					outStreamWriter = new OutputStreamWriter(outStream, "UTF-8");
					
					writer = new BufferedWriter(outStreamWriter);
					
					outStream = null;
					
				} catch (IOException e) {
					throw new OsmosisRuntimeException("Unable to open file for writing.", e);
				} finally {
					if (outStream != null) {
						try {
							outStream.close();
						} catch (Exception e) {
							log.log(Level.SEVERE, "Unable to close output stream.", e);
						}
						outStream = null;
					}
				}
			}
			
			setWriterOnElementWriter(writer);
			
			initialized = true;
			
			write("<?xml version='1.0' encoding='UTF-8'?>");
			writeNewLine();
			
			beginElementWriter();
		}
	}
	
	
	/**
	 * Flushes all changes to file.
	 */
	public void complete() {
		// We need to call this here so that we create empty files if no records
		// are available.
		initialize();

		endElementWriter();

		try {
			if (closeRequired) {
				writer.close();
				writer = null;
			} else if (!writerProvided) {
				writer.flush();
			}

			initialized = false;

		} catch (IOException e) {
			throw new OsmosisRuntimeException("Unable to complete writing to the xml stream.", e);
		}
	}
	
	
	/**
	 * Cleans up any open file handles.
	 */
	public void close() {
		try {
			if (closeRequired) {
				try {
					try {
						if (writer != null) {
							writer.close();
						}
					} catch (IOException e) {
						log.log(Level.SEVERE, "Unable to close writer.", e);
					}
				} finally {
					writer = null;
				}
			}
		} finally {
			initialized = false;
		}
	}
}
