/*
 * Copyright 1999-2010 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.util;

import java.util.List;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Collections;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.File;

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

public class Tail implements Runnable {

    private static final int CHUNK_SIZE = 2048;

    private byte [] buffer;
    private boolean _stop = false;
    private List list = null;
    private Thread _thread;

    private Log _logger;

    public Tail() {
	buffer = new byte[CHUNK_SIZE];
	list = Collections.synchronizedList(new LinkedList());
    }

    public void setLog(Log logger) {
	_logger = logger;
    }

    public void start() {
	_thread = new Thread(this);
	_thread.start();
    }

    class FileWatcher {
	private RandomAccessFile _ras;
	private OutputStream _out;
	private long _pos;

	public FileWatcher(File file, OutputStream out, int pos)
	    throws IOException {
	    _ras = new RandomAccessFile(file, "r");
	    _out = out;
	    _pos = pos;
	}

	public void init()
	    throws IOException {
	    _ras.seek(_pos);
	}

	public long getDiff()
	    throws IOException {
	    return _ras.length() - _pos;
	}

	public void moveBuffer(byte [] buffer, int size)
	    throws IOException {
	    _ras.readFully(buffer, 0, size);
	    _pos += size;

	    if (_logger.isDebugEnabled()) {
                _logger.debug("[tail] output size: " + size);
            }

	    _out.write(buffer, 0, size);
	}

	public void close() {
	    try {
		_ras.close();
	    } catch(Exception e) {}
	    try {
		_out.close();
	    } catch (Exception e) {}
	}
    }

    public void join()
	throws InterruptedException {
	_thread.join();
    }

    public void addFile(File file, OutputStream out, int pos)
	throws IOException {
	list.add(new FileWatcher(file, out, pos));
    }

    public void run() {

	_logger.debug("[tail] running...");

	long len;
	int size;

	Iterator iter = null;
	FileWatcher watcher = null;

	try {
	    iter = list.iterator();
	    while(iter.hasNext()) {
		watcher = (FileWatcher)iter.next();
		watcher.init();
	    }

	    while(!isDone()) {

		try {
		    Thread.sleep(2000);
		} catch(Exception e) {
		}

		iter = list.iterator();
		while(iter.hasNext()) {
		    watcher = (FileWatcher)iter.next();

		    len = watcher.getDiff();
		    if (len <= 0) continue;

		    while(len > 0) {
			size = (len > CHUNK_SIZE) ? CHUNK_SIZE : (int)len;
			watcher.moveBuffer(buffer, size);
			len -= size;
		    }
		}
	    }
	} catch(IOException e) {
	    _logger.debug("Unexpected error.", e);
	} finally {
	    close();
	}

	_logger.debug("[tail] done.");
    }

    private boolean isDone()
	throws IOException {
	if (!_stop) return false;
	Iterator iter = null;
        FileWatcher watcher = null;

	iter = list.iterator();
	while(iter.hasNext()) {
	    watcher = (FileWatcher)iter.next();
	    if (watcher.getDiff() > 0) return false;
	}

	return true;
    }

    private void close() {
        Iterator iter = null;
        FileWatcher watcher = null;
        iter = list.iterator();
        while(iter.hasNext()) {
            watcher = (FileWatcher)iter.next();
            watcher.close();
        }
    }

    public void stop() {
	_logger.debug("[tail] stop called");
	_stop = true;
    }

}
