package lcm.logging;

import java.io.*;

import lcm.util.*;
import lcm.lcm.*;

/**
 * A class for reading and writing LCM log files.
 */
public class Log
{
    BufferedRandomAccessFile raf;

    static final int LOG_MAGIC = 0xEDA1DA01;
    String path;

    /** Used to count the number of messages written so far. **/
    long numMessagesWritten = 0;

    /**
     * Represents a single received LCM message.
     */
    public static class Event
    {
        /**
         * Time of message receipt, represented in microseconds since 00:00:00
         * UTC January 1, 1970.
         */
        public long   utime;
        /**
         * Event number assigned to the message in the log file.
         */
        public long   eventNumber;

        /**
         * Raw data bytes of the message body.
         */
        public byte   data[];

        /**
         * Channel on which the message was received.
         */
        public String channel;
    }

    /**
     * Opens a log file for reading or writing.
     *
     * @param path the filename to open
     * @param mode Specifies the access mode, must be one of "r", "rw",
     * "rws", or "rwd".  See {@link java.io.RandomAccessFile#RandomAccessFile RandomAccessFile} for more detail.
     */
    public Log(String path, String mode) throws IOException
    {
        this.path = path;
        raf = new BufferedRandomAccessFile(path, mode);
        //raf = new RandomAccessFile(path, mode);
    }

    /**
     * Retrieves the path to the log file.
     * @return the path to the log file
     */
    public String getPath()
    {
        return path;
    }

    /**
     * Flush any unwritten data to the underlying file descriptor.
     **/
    public void flush() throws IOException
    {
        raf.flush();
    }

    /**
     * Reads the next event in the log file
     *
     * @throws java.io.EOFException if the end of the file has been reached.
     */
    public synchronized Event readNext() throws IOException
    {
        int magic = 0;
        Event e = new Event();
        int channellen = 0, datalen = 0;

        while (true)
        {
            int v = raf.readByte()&0xff;

            magic = (magic<<8) | v;

            if (magic != LOG_MAGIC)
                continue;

            e.eventNumber = raf.readLong();
            e.utime       = raf.readLong();

            channellen    = raf.readInt();
            datalen       = raf.readInt();

            if (channellen <= 0 || datalen <= 0 || channellen >= 256 || datalen >= 2147483647) {
                System.out.printf("Bad log event eventnumber = 0x%08x utime = 0x%08x channellen = 0x%08x datalen=0x%08x\n",
                                  e.eventNumber, e.utime, channellen, datalen);
                continue;
            }
            break;
        }

        byte bchannel[] = new byte[channellen];
        e.data = new byte[datalen];

        raf.readFully(bchannel);
        e.channel = new String(bchannel);
        raf.readFully(e.data);

        return e;
    }

    public synchronized double getPositionFraction() throws IOException
    {
        return raf.getFilePointer()/((double) raf.length());
    }

    /**
     * Seek to a position in the log file, specified by a fraction.
     *
     * @param frac a number in the range [0, 1)
     */
    public synchronized void seekPositionFraction(double frac) throws IOException
    {
        raf.seek((long) (raf.length()*frac));
    }

    /**
     * Writes an event to the log file. The user is responsible for
     * filling in the eventNumber field, which should be sequentially
     * increasing integers starting with 0.
     */
    public synchronized void write(Event e) throws IOException
    {
        byte[] channelb = e.channel.getBytes();

        raf.writeInt(LOG_MAGIC);
        raf.writeLong(e.eventNumber);
        raf.writeLong(e.utime);
        raf.writeInt(channelb.length);
        raf.writeInt(e.data.length);

        raf.write(channelb, 0, channelb.length);
        raf.write(e.data, 0, e.data.length);
    }

    /** A convenience method for write. It internally manages the
     * eventNumber field, and so calls to this method should not be
     * mixed with calls to the other write methods. **/
    public synchronized void write(long utime, String channel, LCMEncodable msg) throws IOException
    {
        Log.Event le = new Log.Event();
        le.utime = utime;
        le.channel = channel;
        LCMDataOutputStream outs = new LCMDataOutputStream();
        msg.encode(outs);
        le.data = outs.toByteArray();
        le.eventNumber = numMessagesWritten;

        write(le);
        numMessagesWritten++;
    }

    /**
     * Closes the log file and releases and system resources used by it.
     */
    public synchronized void close() throws IOException
    {
        raf.close();
    }
}
