/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
// https://issues.jboss.org/browse/TEIID-4567
// https://issues.redhat.com/browse/TEIID-2163
package org.teiid.core.types;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.nio.charset.Charset;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.SQLException;
import java.sql.SQLXML;

import javax.activation.DataSource;
import javax.sql.rowset.serial.SerialBlob;
import javax.sql.rowset.serial.SerialClob;
import javax.xml.transform.Source;

import org.teiid.core.util.ReaderInputStream;

public abstract class InputStreamFactory implements Source {
	
	public enum StorageMode {
		MEMORY, //TODO: sources may return Serial values that are much too large and we should convert them to persistent.
		PERSISTENT,
		FREE,
		OTHER
	}
	
	public interface StreamFactoryReference {
		
		void setStreamFactory(InputStreamFactory inputStreamFactory);
		
	}
	
	private String systemId;
	protected long length = -1;
	
    /**
     * Get a new InputStream
     * @return
     */
    public abstract InputStream getInputStream() throws IOException;
    
    @Override
    public String getSystemId() {
    	return this.systemId;
    }
    
    @Override
    public void setSystemId(String systemId) {
    	this.systemId = systemId;
    }
    
    /**
	 * @throws IOException  
	 */
    public void free() throws IOException {
    	
    }
    
    /**
     * Length in bytes of the {@link InputStream}
     * @return the length or -1 if the length is not known
     */
    public long getLength() {
		return length;
	}
    
    public void setLength(long length) {
		this.length = length;
	}
    
    /**
	 * @throws IOException  
	 */
    public Reader getCharacterStream() throws IOException {
    	return null;
    }
    
    public StorageMode getStorageMode() {
    	return StorageMode.OTHER;
    }
    
    public static class FileInputStreamFactory extends InputStreamFactory {
    	
    	private File f;
    	
    	public FileInputStreamFactory(File f) {
    		this.f = f;
    		this.setSystemId(f.toURI().toASCIIString());
		}
    	
    	@Override
    	public long getLength() {
    		return f.length();
    	}
    	
    	@Override
    	public InputStream getInputStream() throws IOException {
    		return new BufferedInputStream(new FileInputStream(f));
    	}
    	
    	@Override
    	public StorageMode getStorageMode() {
    		return StorageMode.PERSISTENT;
    	}
    	
    }
    
    public static class ClobInputStreamFactory extends InputStreamFactory implements DataSource {
    	
    	private Clob clob;
    	private Charset charset = Charset.forName(Streamable.ENCODING);
    	
    	public ClobInputStreamFactory(Clob clob) {
    		this.clob = clob;
    	}
    	
    	public Charset getCharset() {
			return charset;
		}
    	
    	public void setCharset(Charset charset) {
			this.charset = charset;
		}
    	
    	@Override
    	public InputStream getInputStream() throws IOException {
    		try {
				return new ReaderInputStream(getReader(clob.getCharacterStream()), charset);
			} catch (SQLException e) {
				throw new IOException(e);
			}
    	}
    	
    	@Override
    	public Reader getCharacterStream() throws IOException {
    		try {
				return getReader(clob.getCharacterStream());
			} catch (SQLException e) {
				throw new IOException(e);
			}
    	}

		@Override
		public String getContentType() {
			return "text/plain"; //$NON-NLS-1$
		}

		@Override
		public String getName() {
			return "clob"; //$NON-NLS-1$
		}

		@Override
		public OutputStream getOutputStream() throws IOException {
			throw new UnsupportedOperationException();
		}
		
		@Override
		public StorageMode getStorageMode() {
			return getStorageMode(clob);
		}
		
		public Reader getReader(Reader reader) {
		    return reader;
		}
    	
    }
    
    public static class BlobInputStreamFactory extends InputStreamFactory implements DataSource {
    	
    	private Blob blob;
    	
    	public BlobInputStreamFactory(Blob blob) {
    		this.blob = blob;
    	}
    	
    	@Override
    	public InputStream getInputStream() throws IOException {
    		try {
				return blob.getBinaryStream();
			} catch (SQLException e) {
				throw new IOException(e);
			}
    	}
    	
    	@Override
    	public long getLength() {
    		if (length == -1) {
	    		try {
					length = blob.length();
				} catch (SQLException e) {
				}
    		}
    		return length;
    	}
    	
    	@Override
		public String getContentType() {
			return "application/octet-stream"; //$NON-NLS-1$
		}

		@Override
		public String getName() {
			return "blob"; //$NON-NLS-1$
		}

		@Override
		public OutputStream getOutputStream() throws IOException {
			throw new UnsupportedOperationException();
		}
		
		@Override
		public StorageMode getStorageMode() {
			return getStorageMode(blob);
		}
    	
    }
    
    public static StorageMode getStorageMode(Object lob) {
    	if (lob instanceof Streamable<?>) {
    		return getStorageMode(((Streamable<?>)lob).getReference());
    	}
    	if (lob instanceof SerialClob) {
    		return StorageMode.MEMORY;
    	}
    	if (lob instanceof SerialBlob) {
    		return StorageMode.MEMORY;
    	}
		if (lob instanceof BaseLob) {
			BaseLob baseLob = (BaseLob)lob;
			try {
				return baseLob.getStreamFactory().getStorageMode();
			} catch (SQLException e) {
				return StorageMode.FREE;
			}
		}
		return StorageMode.OTHER;
    }
    
    public static class SQLXMLInputStreamFactory extends InputStreamFactory implements DataSource {
    	
    	protected SQLXML sqlxml;
    	
    	public SQLXMLInputStreamFactory(SQLXML sqlxml) {
    		this.sqlxml = sqlxml;
    	}
    	
    	@Override
    	public InputStream getInputStream() throws IOException {
    		try {
				return sqlxml.getBinaryStream();
			} catch (SQLException e) {
				throw new IOException(e);
			}
    	}
    	
    	@Override
    	public Reader getCharacterStream() throws IOException {
    		try {
				return sqlxml.getCharacterStream();
			} catch (SQLException e) {
				throw new IOException(e);
			}
    	}
    	
    	@Override
		public String getContentType() {
			return "application/xml"; //$NON-NLS-1$
		}

		@Override
		public String getName() {
			return "sqlxml"; //$NON-NLS-1$
		}

		@Override
		public OutputStream getOutputStream() throws IOException {
			throw new UnsupportedOperationException();
		}
		
		@Override
		public StorageMode getStorageMode() {
			return getStorageMode(sqlxml);
		}
    	
    }
    
}
