package net.sf.fmj.media.protocol.file;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URLConnection;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.Time;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.PullDataSource;
import javax.media.protocol.PullSourceStream;
import javax.media.protocol.Seekable;
import javax.media.protocol.SourceCloneable;

import com.lti.utils.PathUtils;

import net.sf.fmj.media.MimeManager;
import net.sf.fmj.utility.LoggerSingleton;
import net.sf.fmj.utility.URLUtils;

/**
 * File protocol handler DataSource.
 * @author Ken Larson
 *
 */
public class DataSource extends PullDataSource implements SourceCloneable
{
	private static final Logger logger = LoggerSingleton.logger;

	protected RandomAccessFile raf;

	protected boolean connected = false;

	protected ContentDescriptor contentType;

	protected RAFPullSourceStream[] sources;
	

	public DataSource()
	{	
	}


//	public URLDataSource(URL url) throws IOException
//	{	this.url = url;
//		setLocator(new MediaLocator(url));
//		
//	}

	public javax.media.protocol.DataSource createClone()
	{
		final DataSource d = new DataSource();
		d.setLocator(getLocator());
		if (connected)
		{	
			try 
			{
				d.connect();
			} 
			catch (IOException e) 
			{	
				logger.log(Level.WARNING, "" + e, e);
				return null;	// according to the API, return null on failure.
			}
		}
		return d;	
	}


	@Override
	public PullSourceStream[] getStreams()
	{	
		if (!connected)
			throw new Error("Unconnected source.");
		return sources;
	}

	private long contentLength = -1;
	@Override
	public void connect() throws IOException
	{	
		// we allow a re-connection even if we are connected, due to an oddity in the way Manager works. See comments there 
		// in createPlayer(MediaLocator sourceLocator).

		try
		{
			final String path = URLUtils.extractValidPathFromFileUrl(getLocator().toExternalForm());
			if (path == null)
				throw new IOException("Cannot determine valid file path from URL: " + getLocator().toExternalForm());
		
			//logger.fine("Path: " + path);
			raf = new RandomAccessFile(path, "r");
			contentLength = raf.length();
			
			String s = getContentTypeFor(path);	// TODO: use our own mime mapping
			if (s == null)
				throw new IOException("Unknown content type for path: " + path);
			// TODO: what is the right place to apply ContentDescriptor.mimeTypeToPackageName?
			contentType = new ContentDescriptor(ContentDescriptor.mimeTypeToPackageName(s));
			sources = new RAFPullSourceStream[1];
			sources[0] = new RAFPullSourceStream();
			                              
			connected = true;
		}
		catch (IOException e)
		{
			logger.log(Level.WARNING, "" + e, e);
			throw e;
		}
	}

	private static String getContentTypeFor(String path)
	{
		final String ext = PathUtils.extractExtension(path);
		// TODO: what if ext is null?
		String result = MimeManager.getMimeType(ext);
		
		// if we can't find it in our mime table, use URLConnection's
		
		if (result != null)
			return result;
		
		result = URLConnection.getFileNameMap().getContentTypeFor(path);
		return result;
		
	}
	
//	public static String getFileName(MediaLocator locator)
//	{
//		String path = getLocator().getRemainder();
//		
//		// Try removing different #s of / from the beginning
//		if (path.startsWith("//"))
//			path = path.substring(2);	// TODO: handle stripping
//	}
	
	@Override
	public String getContentType()
	{	if (!connected)
			throw new Error("Source is unconnected.");
		String path = getLocator().getRemainder();
		String s = getContentTypeFor(path);	// TODO: use our own mime mapping
		return ContentDescriptor.mimeTypeToPackageName(s);
	}

	@Override
	public void disconnect()
	{	if (!connected)
			return;
		// TODO: what to do?
		connected = false;
	}

	@Override
	public void start() throws java.io.IOException
	{	
		//throw new UnsupportedOperationException(); // TODO - what to do?
	}

	@Override
	public void stop() throws java.io.IOException
	{	//throw new UnsupportedOperationException(); // TODO - what to do?
	}

	@Override
	public Time getDuration()
	{	return Time.TIME_UNKNOWN;	// TODO: any case where we know the duration?
	}

	@Override
	public Object[] getControls()
	{	return new Object[0];
	}

	@Override
	public Object getControl(String controlName)
	{	return null;
	}

	class RAFPullSourceStream implements PullSourceStream, Seekable
	{

		private boolean endOfStream = false;
		public boolean endOfStream()
		{
			return endOfStream;
		}

		public ContentDescriptor getContentDescriptor()
		{
			return contentType;
		}

		public long getContentLength()
		{
			return contentLength;
		}

		public int read(byte[] buffer, int offset, int length) throws IOException
		{
			final int result = raf.read(buffer, offset, length);	// TODO: does this handle the requirement of not returning 0 unless passed in 0?
			if (result == -1) // end of stream
				endOfStream = true;
			
			return result;
		}

		public boolean willReadBlock()
		{
			return false;
		}

		public Object getControl(String controlType)
		{
			return null;
		}

		public Object[] getControls()
		{
			return new Object[0];
		}

		public boolean isRandomAccess()
		{
			return true;
		}

		public long seek(long where)
		{
			try
			{
				raf.seek(where);
				
				return raf.getFilePointer();
			} catch (IOException e)
			{
				logger.log(Level.WARNING, "" + e, e);	// TODO: what to return
				throw new RuntimeException(e);
			}
		}

		public long tell()
		{
			try
			{
				return raf.getFilePointer();
			} catch (IOException e)
			{
				logger.log(Level.WARNING, "" + e, e);	// TODO: what to return
				throw new RuntimeException(e);
			}
		}
	}
}

