/*
 * BufferLoadRequest.java - I/O request
 * :tabSize=4:indentSize=4:noTabs=false:
 * :folding=explicit:collapseFolds=1:
 *
 * Copyright (C) 2000, 2005 Slava Pestov
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package org.gjt.sp.jedit.bufferio;

//{{{ Imports
import java.io.*;
import java.nio.charset.*;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.zip.GZIPInputStream;
import org.gjt.sp.jedit.io.*;
import org.gjt.sp.jedit.*;
import org.gjt.sp.jedit.buffer.JEditBuffer;
import org.gjt.sp.util.*;
//}}}

/**
 * A buffer load request.
 * @author Slava Pestov
 * @version $Id: BufferLoadRequest.java 23224 2013-09-30 20:51:42Z shlomy $
 */
public class BufferLoadRequest extends BufferIORequest
{
	//{{{ BufferLoadRequest constructor
	/**
	 * Creates a new buffer I/O request.
	 * @param view The view
	 * @param buffer The buffer
	 * @param session The VFS session
	 * @param vfs The VFS
	 * @param path The path
	 */
	public BufferLoadRequest(View view, Buffer buffer,
		Object session, VFS vfs, String path)
	{
		super(view,buffer,session,vfs,path);
	} //}}}

	//{{{ run() method
	public void _run()
	{
		try
		{
			setCancellable(true);
			if(!buffer.isTemporary())
			{
				String[] args = { vfs.getFileName(path) };
				setStatus(jEdit.getProperty("vfs.status.load",args));
				setValue(0L);
			}

			path = vfs._canonPath(session,path,view);

			readContents();
			buffer.setNewFile(false);

			if (jEdit.getBooleanProperty("persistentMarkers") &&
			    (vfs.isMarkersFileSupported()))
			{
				InputStream markers = null;
				try
				{
					String[] args = { vfs.getFileName(path) };
					if(!buffer.isTemporary())
						setStatus(jEdit.getProperty("vfs.status.load-markers",args));
					setCancellable(true);

					markers = vfs._createInputStream(session,markersPath,true,view);
					if(markers != null)
						readMarkers(buffer,markers);
				}
				catch(Exception e)
				{
					// ignore
				}
				finally
				{
					IOUtilities.closeQuietly(markers);
				}
			}
		}
		catch(InterruptedException e)
		{
			buffer.setBooleanProperty(ERROR_OCCURRED,true);
			Thread.currentThread().interrupt();
		}
		catch(Exception e)
		{
			Log.log(Log.ERROR,this,e);
			Object[] pp = { e.toString() };
			VFSManager.error(view,path,"ioerror.read-error",pp);

			buffer.setBooleanProperty(ERROR_OCCURRED,true);
		}
		catch(OutOfMemoryError oom)
		{
			Log.log(Log.ERROR,this,oom);
			VFSManager.error(view,path,"out-of-memory-error",null);

			buffer.setBooleanProperty(ERROR_OCCURRED,true);
		}
		finally
		{
			try
			{
				vfs._endVFSSession(session,view);
			}
			catch(Exception e)
			{
				Log.log(Log.ERROR,this,e);
				String[] pp = { e.toString() };
				VFSManager.error(view,path,"ioerror.read-error",pp);

				buffer.setBooleanProperty(ERROR_OCCURRED,true);
			}
		}
	} //}}}

	//{{{ getNakedStream() method
	/**
	 * Returns the raw contents stream for this load request.
	 * This stream is not buffered or unzipped.
	 */
	private InputStream getNakedStream() throws IOException
	{
		InputStream in = vfs._createInputStream(session,path,false,view);
		if(in != null)
		{
			return in;
		}
		throw new IOException("Unable to get a Stream for " + path);
	} //}}}

	//{{{ getContentLength() method
	/**
	 * Returns content length of this load request.
	 */
	private long getContentLength() throws IOException
	{
		VFSFile entry = vfs._getFile(session,path,view);
		if(entry != null)
			return entry.getLength();
		else
			return 0L;
	} //}}}

	//{{{ rewindContentsStream() method
	/**
	 * Returns rewinded contents stream.
	 * This method assumes the marked stream was made by
	 * getMarkedStream() method. The stream may be reopened if reset()
	 * failed.
	 */
	private BufferedInputStream rewindContentsStream(BufferedInputStream markedStream, boolean gzipped)
		throws IOException
	{
		try
		{
			markedStream.reset();
			return markedStream;
		}
		catch(IOException e)
		{
			Log.log(Log.NOTICE, this
				, path + ": Reopening to rewind the stream");
			// Reopen the stream because the mark has been
			// invalidated while previous reading.
			markedStream.close();
			InputStream in = getNakedStream();
			try
			{
				if(gzipped)
				{
					in = new GZIPInputStream(in);
				}
				BufferedInputStream result
					= AutoDetection.getMarkedStream(in);
				in = null;
				return result;
			}
			finally
			{
				IOUtilities.closeQuietly((Closeable)in);
			}
		}
	} //}}}

	//{{{ readContents() method
	/**
	 * Read the contents of this load request.
	 * Some auto detection is performed if enabled.
	 *   - GZIPed file
	 *   - The encoding
	 * If fallback encodings are specified, they are used on
	 * encoding errors.
	 * @throws InterruptedException
	 */
	private void readContents() throws IOException, InterruptedException
	{
		long length = getContentLength();

		BufferedInputStream markedStream
			= AutoDetection.getMarkedStream(getNakedStream());
		try
		{
			boolean gzipped = false;
			// encodingProviders is consist of given
			// encodings as String or contents-aware
			// detectors as EncodingDetector.
			List<Object> encodingProviders
				= new ArrayList<Object>();

			boolean autodetect = buffer.getBooleanProperty(Buffer.ENCODING_AUTODETECT);
			if(autodetect)
			{
				gzipped = AutoDetection.isGzipped(markedStream);
				markedStream.reset();

				encodingProviders.addAll(AutoDetection.getEncodingDetectors());
				// If the detected encoding fail, fallback to
				// the original encoding.
				encodingProviders.add(buffer.getStringProperty(JEditBuffer.ENCODING));

				String fallbackEncodings = jEdit.getProperty("fallbackEncodings");
				if(fallbackEncodings != null && fallbackEncodings.length() > 0)
					Collections.addAll(encodingProviders, fallbackEncodings.split("\\s+"));
			}
			else
			{
				gzipped = buffer.getBooleanProperty(Buffer.GZIPPED);
				encodingProviders.add(buffer.getStringProperty(JEditBuffer.ENCODING));
			}

			if(gzipped)
			{
				Log.log(Log.DEBUG, this, path + ": Stream is gzipped.");
				markedStream = AutoDetection.getMarkedStream(
					new GZIPInputStream(markedStream));
			}

			Set<String> failedEncodings = new HashSet<String>();
			Exception encodingError = null;
			for(Object encodingProvider: encodingProviders)
			{
				String encoding = null;
				if (encodingProvider instanceof String)
				{
					encoding = (String)encodingProvider;
				}
				else if(encodingProvider instanceof EncodingDetector)
				{
					markedStream = rewindContentsStream(markedStream, gzipped);
					encoding = ((EncodingDetector)encodingProvider).detectEncoding(new BufferedInputStream(markedStream));
				}
				else
				{
					Log.log(Log.DEBUG, this, "Strange encodingProvider: " + encodingProvider);
				}

				if(encoding == null || encoding.length() <= 0
					|| failedEncodings.contains(encoding))
				{
					continue;
				}

				markedStream = rewindContentsStream(markedStream, gzipped);
				try
				{
					read(EncodingServer.getTextReader(markedStream, encoding)
						, length, false);
					if(autodetect)
					{
						// Store the successful properties.
						if(gzipped)
						{
							buffer.setBooleanProperty(Buffer.GZIPPED,true);
						}
						buffer.setProperty(JEditBuffer.ENCODING, encoding);
					}
					return;
				}
				catch(CharConversionException e)
				{
					encodingError = e;
				}
				catch(CharacterCodingException e)
				{
					encodingError = e;
				}
				catch(UnsupportedEncodingException e)
				{
					encodingError = e;
				}
				catch(UnsupportedCharsetException e)
				{
					encodingError = e;
				}
				Log.log(Log.NOTICE, this, path + ": " + encoding
					+ ": " + encodingError);
				failedEncodings.add(encoding);
			}
			// All possible detectors and encodings failed.
			Object[] pp = { TextUtilities.join(failedEncodings,","), "" };
			if(failedEncodings.size() < 2)
			{
				pp[1] = encodingError.toString();
			}
			else
			{
				pp[1] = "See details in Activity Log";
			}
			VFSManager.error(view,path,"ioerror.encoding-error",pp,Log.NOTICE);
			markedStream = rewindContentsStream(markedStream, gzipped);
			read(EncodingServer.getEncoding(
				buffer.getStringProperty(JEditBuffer.ENCODING)
				).getPermissiveTextReader(markedStream)
				, length, false);
			if(autodetect && gzipped)
			{
				buffer.setBooleanProperty(Buffer.GZIPPED,true);
			}
		}
		finally
		{
			markedStream.close();
		}
	} //}}}

	//{{{ readMarkers() method
	private static void readMarkers(Buffer buffer, InputStream _in)
		throws IOException, InterruptedException
	{
		// For `reload' command
		buffer.removeAllMarkers();

		BufferedReader in = new BufferedReader(new InputStreamReader(_in));

		try
		{
			String line;
			while((line = in.readLine()) != null)
			{
				if(Thread.interrupted())
					throw new InterruptedException();

				// malformed marks file?
				if(line.length() == 0)
					continue;

				// compatibility kludge for jEdit 3.1 and earlier
				if(line.charAt(0) != '!')
					continue;


				char shortcut = line.charAt(1);
				int start = line.indexOf(';');
				int end = line.indexOf(';',start + 1);
				int position = Integer.parseInt(line.substring(start + 1,end));
				buffer.addMarker(shortcut,position);
			}
			buffer.setMarkersChanged(false);
		}
		finally
		{
			in.close();
		}
	} //}}}
}
