
package uk.co.wingpath.modbus;

import java.text.*;
import java.util.*;
import uk.co.wingpath.util.*;
import uk.co.wingpath.io.*;

/**
* This class provides tracing and logging of Modbus messages.
*/
public class Tracer
{
    private Logger logger;
    private boolean traceEnabled = false;
    private boolean rawTraceEnabled = false;
    private boolean logReadEnabled = false;
    private boolean logWriteEnabled = false;
    private boolean includeTime = true;
    private boolean transMarkerEnabled = true;
    private String transMarker = "------------";
    private String prefix;
    private Connection connection;

    /** Constructs a {@code Tracer} using the specified logger.
    * @param logger logger to output trace/log lines.
    */
    public Tracer (Logger logger)
    {
        this.logger = logger;
        prefix = "";
        connection = null;
    }

    /**
    * Sets the connection that is being traced. This is used only for
    * getting the connection name (which can change dynamically for a UDP
    * connection).
    * @param connection connection being traced.
    */
    public void setConnection (Connection connection)
    {
        this.connection = connection;
    }

    /**
    * Sets the prefix to be used to distinguish master and slave messages.
    * Modsak's monitor mode uses "M" and "S". For other cases it can be
    * left at the default value of "".
    * @param prefix string to be output after the time and before the message.
    */
    public void setPrefix (String prefix)
    {
        this.prefix = prefix;
    }

    /**
    * Sets whether the time should be prefixed to trace messages.
    * @param includeTime whether to include time in messages. Default is
    * {@code true}.
    */
    public void setIncludeTime (boolean includeTime)
    {
        this.includeTime = includeTime;
    }

    /**
    * Enable/disable marker line at the end of a transaction.
    * @param enabled whether to write marker line.
    */
    public void setTransMarkerEnabled (boolean enabled)
    {
        transMarkerEnabled = enabled;
    }

    /**
    * Returns the enabled state of transaction marker lines.
    * @return {@code true} if transaction marker lines are enabled.
    */
    public boolean isTransMarkerEnabled ()
    {
        return transMarkerEnabled;
    }

    /**
    * Sets the string to be used in the line marking the end of a transaction.
    * @param marker string to mark the end of a transaction.
    */
    public void setTransMarker (String marker)
    {
        if (marker == null)
            throw new NullPointerException ();
        transMarker = marker;
    }

    /**
    * Enable/disable interpreted tracing of Modbus messages.
    * @param enabled whether to enable tracing.
    */
    public void setTraceEnabled (boolean enabled)
    {
        traceEnabled = enabled;
    }

    /**
    * Enable/disable raw tracing of sent/received data.
    * @param enabled whether to enable tracing.
    */
    public void setRawTraceEnabled (boolean enabled)
    {
        rawTraceEnabled = enabled;
    }

    /**
    * Enable/disable logging of Modbus register reads.
    * @param enabled whether to enable logging.
    */
    public void setLogReadEnabled (boolean enabled)
    {
        logReadEnabled = enabled;
    }

    /**
    * Enable/disable logging of Modbus register writes.
    * @param enabled whether to enable logging.
    */
    public void setLogWriteEnabled (boolean enabled)
    {
        logWriteEnabled = enabled;
    }

    /**
    * Returns the enabled state of interpreted tracing.
    * @return {@code true} if tracing is enabled.
    */
    public boolean isTraceEnabled ()
    {
        return traceEnabled;
    }

    /**
    * Returns the enabled state of raw tracing.
    * @return {@code true} if tracing is enabled.
    */
    public boolean isRawTraceEnabled ()
    {
        return rawTraceEnabled;
    }

    /**
    * Returns the enabled state of register read logging.
    * @return {@code true} if logging is enabled.
    */
    public boolean isLogReadEnabled ()
    {
        return logReadEnabled;
    }

    /**
    * Returns the enabled state of register write logging.
    * @return {@code true} if logging is enabled.
    */
    public boolean isLogWriteEnabled ()
    {
        return logWriteEnabled;
    }

    private void traceLine (String helpId, String msg)
    {
        if (includeTime)
        {
            long ms = System.currentTimeMillis ();
            logger.writeLine (helpId,
                String.format ("%1$tH:%1$tM:%1$tS.%1$tL: %2$s%3$s",
                    ms, prefix, msg));
        }
        else
        {
            logger.writeLine (helpId, prefix + msg);
        }
    }

    /**
    * Writes an interpreted trace message.
    * <p>If interpreted tracing is enabled, this method writes the specified
    * message preceded by the time.
    * @param helpId help identifier for any help associated with the message.
    * @param msg the message to be written.
    * {@code null} if there is no associated help.
    */
    public void trace (String helpId, String msg)
    {
        if (traceEnabled)
            traceLine (helpId, msg);
    }

    /**
    * Traces interpreted sent/received data.
    * <p>If raw tracing is enabled, this methods writes the time, followed
    * by the specified prefix, followed by the specified message.
    * @param helpId help identifier for any help associated with the message.
    * @param dirPrefix prefix to be output before the data ("<" or ">").
    * @param msg the message to be written.
    * {@code null} if there is no associated help.
    */
    public void trace (String helpId, String dirPrefix, String msg)
    {
        String name = connection != null ? connection.getName () : "";
        if (traceEnabled)
        {
            traceLine (helpId, dirPrefix + name + msg);
        }
    }


    /**
    * Traces raw sent/received data.
    * <p>If raw tracing is enabled, this methods writes the time, followed
    * by the specified prefix, followed by the specified data in hex.
    * @param dirPrefix prefix to be output before the data ("<" or ">").
    * @param data array containing data to be traced.
    * @param offset offset in {@code data} of the first byte to be traced.
    * @param len number of bytes of data to be traced.
    */
    public void traceRaw (String dirPrefix, byte [] data, int offset, int len)
    {
        if (rawTraceEnabled)
        {
            String name = connection != null ? connection.getName () : "";
            traceLine (null,
                dirPrefix + name + " " + Bytes.toHexString (data, offset, len));
        }
    }

    /**
    * Logs a register read message.
    * <p>If register read logging is enabled, this method writes the specified
    * message preceded by the time.
    * @param msg the message to be written.
    */
    public void logRead (String msg)
    {
        if (logReadEnabled)
        {
            long ms = System.currentTimeMillis ();
            logger.writeLine (null,
                String.format ("%1$tH:%1$tM:%1$tS.%1$tL: %2$s", ms, msg));
        }
    }

    /**
    * Logs a register write message.
    * <p>If register write logging is enabled, this method writes the specified
    * message preceded by the time.
    * @param msg the message to be written.
    */
    public void logWrite (String msg)
    {
        if (logWriteEnabled)
        {
            long ms = System.currentTimeMillis ();
            logger.writeLine (null,
                String.format ("%1$tH:%1$tM:%1$tS.%1$tL: %2$s", ms, msg));
        }
    }

    /**
    * Marks the end of a transaction in the log.
    */
    public void endTransaction ()
    {
        if (transMarkerEnabled && (traceEnabled || rawTraceEnabled))
            logger.writeLine (null, transMarker);
    }
}

