/*
 * Copyright 2014-2019 Real Logic Ltd.
 *
 * 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
 *
 * https://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 io.aeron.agent;

import org.agrona.CloseHelper;
import org.agrona.LangUtil;
import org.agrona.MutableDirectBuffer;
import org.agrona.concurrent.Agent;
import org.agrona.concurrent.MessageHandler;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;

import static io.aeron.agent.EventConfiguration.EVENT_READER_FRAME_LIMIT;
import static io.aeron.agent.EventConfiguration.EVENT_RING_BUFFER;
import static java.nio.file.StandardOpenOption.*;

/**
 * Simple reader of {@link EventConfiguration#EVENT_RING_BUFFER} that appends to {@link System#out} by default
 * or to file if {@link #LOG_FILENAME_PROP_NAME} System property is set.
 */
public class EventLogReaderAgent implements Agent, MessageHandler
{
    /**
     * Event Buffer length system property name. If not set then output will default to {@link System#out}.
     */
    public static final String LOG_FILENAME_PROP_NAME = "aeron.event.log.filename";

    private final FileChannel fileChannel;
    final CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
    final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(
        EventConfiguration.MAX_EVENT_LENGTH + System.lineSeparator().length());
    private final StringBuilder builder = new StringBuilder();
    private final EventCodeIdentifier identifier = new EventCodeIdentifier();

    public EventLogReaderAgent()
    {
        final String filename = System.getProperty(LOG_FILENAME_PROP_NAME);
        if (null == filename)
        {
            fileChannel = null;
        }
        else
        {
            try
            {
                fileChannel = FileChannel.open(Paths.get(filename), CREATE, APPEND, WRITE);
            }
            catch (final IOException ex)
            {
                throw new RuntimeException(ex);
            }
        }

        builder.setLength(0);
        DriverEventDissector.dissectLogStartMessage(System.nanoTime(), System.currentTimeMillis(), builder);
        builder.append(System.lineSeparator());

        if (null == fileChannel)
        {
            System.out.print(builder);
        }
        else
        {
            write(byteBuffer, fileChannel);
        }
    }

    public void onClose()
    {
        CloseHelper.close(fileChannel);
    }

    public String roleName()
    {
        return "event-log-reader";
    }

    public int doWork()
    {
        return EVENT_RING_BUFFER.read(this, EVENT_READER_FRAME_LIMIT);
    }

    public void onMessage(final int msgTypeId, final MutableDirectBuffer buffer, final int index, final int length)
    {
        populateIdentifier(msgTypeId, identifier);
        builder.setLength(0);
        if (DriverEventCode.EVENT_CODE_TYPE == identifier.eventCodeTypeId)
        {
            DriverEventCode.get(identifier.eventCodeId).decode(buffer, index, builder);
        }
        else if (ClusterEventCode.EVENT_CODE_TYPE == identifier.eventCodeTypeId)
        {
            ClusterEventCode.get(identifier.eventCodeId).decode(buffer, index, builder);
        }
        else if (ArchiveEventCode.EVENT_CODE_TYPE == identifier.eventCodeTypeId)
        {
            ArchiveEventCode.get(identifier.eventCodeId).decode(buffer, index, builder);
        }
        else
        {
            builder.append("Unknown EventCodeType: ").append(identifier.eventCodeTypeId);
        }
        builder.append(System.lineSeparator());

        if (null == fileChannel)
        {
            System.out.print(builder);
        }
        else
        {
            write(byteBuffer, fileChannel);
        }
    }

    private void write(final ByteBuffer buffer, final FileChannel fileChannel)
    {
        try
        {
            buffer.clear();
            encoder.reset();
            final CoderResult coderResult = encoder.encode(CharBuffer.wrap(builder), buffer, false);

            if (CoderResult.UNDERFLOW != coderResult)
            {
                coderResult.throwException();
            }

            buffer.flip();

            do
            {
                fileChannel.write(buffer);
            }
            while (buffer.remaining() > 0);
        }
        catch (final Exception ex)
        {
            LangUtil.rethrowUnchecked(ex);
        }
    }

    private static void populateIdentifier(final int msgTypeId, final EventCodeIdentifier identifier)
    {
        identifier.eventCodeTypeId = msgTypeId >> 16;
        identifier.eventCodeId = msgTypeId & 0xFFFF;
    }

    private static final class EventCodeIdentifier
    {
        private int eventCodeTypeId;
        private int eventCodeId;
    }
}

@@ -1752,43 +1752,8 @@
 nal 
-FileChannel fileChannel;%0A    final 
 Char
@@ -1810,24 +1810,32 @@
 oder();%0A    
+private 
 final ByteBu
@@ -2038,104 +2038,63 @@
 ate 
-final EventCodeIdentifier identifier = new EventCodeIdentifier();%0A%0A    public EventLogReaderAgen
+FileChannel fileChannel = null;%0A%0A    public void onStar
 t()%0A
@@ -2188,25 +2188,25 @@
    if (null 
-=
+!
 = filename)%0A
@@ -2219,73 +2219,8 @@
   %7B%0A
-            fileChannel = null;%0A        %7D%0A        else%0A        %7B%0A
     
@@ -2426,34 +2426,33 @@
     
-throw new RuntimeException
+LangUtil.rethrowUnchecked
 (ex)
@@ -2910,24 +2910,52 @@
 leChannel);%0A
+        fileChannel = null;%0A
     %7D%0A%0A    p
@@ -3268,50 +3268,105 @@
     
-populateIdentifier(msgTypeId, identifier);
+final int eventCodeTypeId = msgTypeId %3E%3E 16;%0A        final int eventCodeId = msgTypeId & 0xFFFF;%0A
 %0A   
@@ -3384,32 +3384,33 @@
 r.setLength(0);%0A
+%0A
         if (Driv
@@ -3436,35 +3436,24 @@
 ODE_TYPE == 
-identifier.
 eventCodeTyp
@@ -3495,35 +3495,24 @@
 entCode.get(
-identifier.
 eventCodeId)
@@ -3603,35 +3603,24 @@
 ODE_TYPE == 
-identifier.
 eventCodeTyp
@@ -3663,35 +3663,24 @@
 entCode.get(
-identifier.
 eventCodeId)
@@ -3775,27 +3775,16 @@
 TYPE == 
-identifier.
 eventCod
@@ -3835,27 +3835,16 @@
 ode.get(
-identifier.
 eventCod
@@ -3974,27 +3974,16 @@
 .append(
-identifier.
 eventCod
@@ -3994,32 +3994,33 @@
 eId);%0A        %7D%0A
+%0A
         builder.
@@ -4898,363 +4898,7 @@
    %7D
-%0A%0A    private static void populateIdentifier(final int msgTypeId, final EventCodeIdentifier identifier)%0A    %7B%0A        identifier.eventCodeTypeId = msgTypeId %3E%3E 16;%0A        identifier.eventCodeId = msgTypeId & 0xFFFF;%0A    %7D%0A%0A    private static final class EventCodeIdentifier%0A    %7B%0A        private int eventCodeTypeId;%0A        private int eventCodeId;%0A    %7D
 %0A%7D%0A

Change EventLogReaderAgent so it does not suffer from false sharing and can benefit from biased locking.