/*
 * Copyright 1999-2006 University of Chicago
 *
 * 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 org.globus.ftp;

import java.io.IOException;
import java.io.OutputStream;
import java.io.EOFException;
import java.io.InterruptedIOException;

import org.globus.util.CircularBuffer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class OutputStreamDataSource implements DataSource {

    protected CircularBuffer buffers = null;
    private DataOutputStream out;

    public OutputStreamDataSource(int size) {
	this.buffers = new CircularBuffer(5);
	this.out = new DataOutputStream(size);
    }

    // returns null if EOF
    public Buffer read()
	throws IOException {
	try {
	    return (Buffer)this.buffers.get();
	} catch (InterruptedException e) {
	    // that should not happen
	    throw new InterruptedIOException();
	}
    }

    public void close()
	throws IOException {
	this.buffers.interruptBoth();
	this.out.setClosed();
    }

    public OutputStream getOutputStream() {
	return this.out;
    }

    public long totalSize()
	throws  IOException {
        return -1;
    }

    // if write() is blocked, it will not be unblocked by close()
    class DataOutputStream extends OutputStream {

	protected byte[] buff;
	protected int index;
	private boolean closed = false;

	public DataOutputStream(int size) {
	    this.buff = new byte[size];
	    this.index = 0;
	}

	public void write(byte[] data)
	    throws IOException {
	    write(data, 0, data.length);
	}

	public synchronized void write(byte[] data, int off, int len)
	    throws IOException {
	    if (isClosed()) {
		throw new EOFException();
	    }
	    int max;
	    while (len > 0) {
		if (this.index + len > this.buff.length) {
		    max = (this.buff.length - this.index);
		    System.arraycopy(data, off, this.buff, this.index, max);
		    this.index += max;
		    flush();
		    len -= max;
		    off += max;
		} else {
		    System.arraycopy(data, off, this.buff, this.index, len);
		    this.index += len;
		    break;
		}
	    }
	}

	public synchronized void write(int b)
	    throws IOException {
	    if (isClosed()) {
		throw new EOFException();
	    }
	    if (this.index == this.buff.length) {
		flush();
	    }

	    buff[index++] = (byte)b;
	}

	public synchronized void flush()
	    throws IOException {
	    if (this.index == 0) {
		return;
	    }
	    byte [] data = new byte[this.index];
	    System.arraycopy(this.buff, 0, data, 0, this.index);
	    Buffer b = new Buffer(data, this.index);
	    try {
		if (!buffers.put(b)) {
		    throw new EOFException();
		}
	    } catch (InterruptedException e) {
		// that should never happen
		throw new InterruptedIOException();
	    }
	    this.index = 0;
	}

	public void close()
	    throws IOException {
	    flush();
	    // will let get run until it returns null
	    // and will throe EOFException on next put call
	    // but existing put calls will not be interrupted
	    setClosed();
	    buffers.closePut();
	}

	protected synchronized void setClosed() {
	    this.closed = true;
	}

	private synchronized boolean isClosed() {
	    return this.closed;
	}

    }
}
