package lcm.lcm;

import java.net.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import java.nio.*;

import lcm.logging.*;

public class LogFileProvider implements Provider
{
    LCM lcm;
    Log log;

    double speed; // how fast do we play? <=0 for "as fast as possible"
    double delay; // how many seconds to delay before starting to play? (crude race-condition hack)
    boolean verbose; // report actual speed periodically
    double skip; // skip a fraction of the log file [0, 1.0]

    boolean writemode;
    long nanotime_start;
    long utime_start;

    ReaderThread reader;

    public LogFileProvider(LCM lcm, URLParser up) throws IOException
    {
	this.lcm = lcm;

	String logPath = up.get("network","");

	speed = up.get("speed", 1.0);
	delay = up.get("delay", 0.5);
	verbose = up.get("verbose", false);
	skip = up.get("skip", 0.0); // skip this fraction of the log file.
	writemode = up.get("mode", "r").equals("w");

	if(writemode) {
	    log = new Log(logPath, "w");
	    nanotime_start = System.nanoTime();
	    utime_start = System.currentTimeMillis() * 1000;
	} else {
	    log = new Log(logPath, "r");
	    reader = new ReaderThread();
	    reader.start();
	}
    }

    boolean publishWarned = false;
    public synchronized void publish(String channel, byte data[], int offset, int length) 
    {
	if(!writemode) {
	    if (publishWarned)
		return;
	    System.err.println("LogFileProvider opened in read mode, no publishing allowed.");
	    publishWarned = true;
	}

	Log.Event event = new Log.Event();
	event.utime = utime_start + (System.nanoTime() - nanotime_start) / 1000;
	event.eventNumber = 0;
	event.data = new byte[length];
	System.arraycopy(data, offset, event.data, 0, length);
	event.channel = channel;
	try {
	    log.write(event);
	} catch (IOException ex) {
	    System.err.println("ex: "+ex);
	}
    }

    public synchronized void subscribe(String channel) { }

    public synchronized void close() {
	if (reader != null) {
	    reader.interrupt();
	    try {
		reader.join();
	    } catch (InterruptedException ex) {
	    }
	}
	reader = null;
	try {
	    log.close();
	} catch (IOException ex) {
	}
	log = null;
    }

    class ReaderThread extends Thread
    {
	ReaderThread()
	{
	    setDaemon(true);
	}

	public void run()
	{
	    try {
		runEx();
	    } catch (InterruptedException ex) {
		return;
	    } catch (IOException ex) {
		System.err.println("ex: "+ex);
	    }
	}

	void runEx() throws IOException, InterruptedException
	{
	    log.seekPositionFraction(skip);

	    while (lcm.getNumSubscriptions()==0)
		Thread.sleep(10);

	    Thread.sleep((int) (delay*1000));

	    long lastLocalUtime = System.nanoTime()/1000;
	    long lastEventUtime = -1;
	    double delayAccumulator = 0; // how long do we need to delay in real time?

	    double verboseAccumulator = 0;
	    long   verboseLastEventUtime = -1;

	    while (true) {
		Log.Event ev = log.readNext();

		// how much time elapsed when the log was recorded?
		// we have to wait some fraction of the time...
		if (lastEventUtime > 0) {
		    double dt = (ev.utime - lastEventUtime) / 1000000.0;
		    if (dt > 0 && speed > 0)
			delayAccumulator += dt / speed;
		}
		lastEventUtime = ev.utime;

		// subtract any clock time that has elapsed.
		long localUtime = System.nanoTime()/1000;
		double dt = (localUtime - lastLocalUtime)/1000000.0;
		lastLocalUtime = localUtime;
		delayAccumulator -= dt;
		verboseAccumulator += dt;

		// spit out some info at 1Hz
		if (verboseAccumulator > 1.0 && verbose) {
		    double eventDt = (lastEventUtime - verboseLastEventUtime)/1000000.0;
		    verboseLastEventUtime = lastEventUtime;
		    System.err.printf("LogFile: rate = %8.3f, position = %8.3f %%\n", 
				      eventDt/verboseAccumulator,
				      log.getPositionFraction()*100.0);
		    verboseAccumulator = 0;
		}

		// sleep if necessary.
		if (delayAccumulator > 0.001) {
		    int ms = (int) (delayAccumulator*1000);
		    Thread.sleep(ms);
		}

		// dispatch the message
		lcm.receiveMessage(ev.channel, ev.data, 0, ev.data.length);
	    }
	}
    }
}
