/*
 * Copyright 2015 Philadelphia authors
 *
 * 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 com.paritytrading.philadelphia;

import static com.paritytrading.philadelphia.FIX.*;
import static com.paritytrading.philadelphia.FIXTags.*;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * A message parser.
 */
public class FIXMessageParser {

    private final FIXMessageListener listener;

    private final FIXMessage message;

    private final boolean checkSumEnabled;

    private final FIXValue beginString;
    private final FIXValue bodyLength;
    private final FIXValue checkSum;

    /**
     * Create a message parser.
     *
     * @param config the parser configuration
     * @param listener the message listener
     */
    public FIXMessageParser(FIXConfig config, FIXMessageListener listener) {
        this.message = new FIXMessage(config.getMaxFieldCount(), config.getFieldCapacity());

        this.checkSumEnabled = config.isCheckSumEnabled();

        this.listener = listener;

        this.beginString = new FIXValue(BEGIN_STRING_FIELD_CAPACITY);
        this.bodyLength  = new FIXValue(BODY_LENGTH_FIELD_CAPACITY);
        this.checkSum    = new FIXValue(CHECK_SUM_FIELD_CAPACITY);
    }

    /**
     * Attempt to parse a message from a buffer. If a message is parsed
     * successfully, invoke the message listener.
     *
     * @param buffer a buffer
     * @return true if a message was parsed successfully, otherwise false
     * @throws IOException if an I/O error occurs
     */
    public boolean parse(ByteBuffer buffer) throws IOException {
        int tag;

        while (true) {
            buffer.mark();

            int beginning = buffer.position();

            // Partial message
            tag = FIXTags.get(buffer);
            if (tag == 0) {
                buffer.reset();

                return false;
            }

            // Partial message
            if (!beginString.get(buffer)) {
                buffer.reset();

                return false;
            }

            // Garbled message
            if (tag != BeginString)
                continue;

            int position = buffer.position();

            // Partial message
            tag = FIXTags.get(buffer);
            if (tag == 0) {
                buffer.reset();

                return false;
            }

            // Partial message
            if (!bodyLength.get(buffer)) {
                buffer.reset();

                return false;
            }

            // Garbled message
            if (tag != BodyLength) {
                buffer.position(position);

                continue;
            }

            int length = (int)bodyLength.asInt();

            // Partial message
            if (buffer.remaining() < length + 7) {
                buffer.reset();

                return false;
            }

            position = buffer.position();

            if (checkSumEnabled && !acceptCheckSum(buffer, beginning, position, length))
                continue;

            int limit = buffer.limit();

            buffer.limit(position + length);

            // Garbled message
            boolean garbled = message.get(buffer) == false;

            buffer.limit(limit);
            buffer.position(position + length + 7);

            if (garbled)
                continue;

            listener.message(message);

            return true;
        }
    }

    private boolean acceptCheckSum(ByteBuffer buffer, int beginning, int position, int length) throws IOException {
        buffer.position(position + length);

        // Garbled message
        int tag = FIXTags.get(buffer);
        if (tag == 0)
            return false;

        // Garbled message
        if (!checkSum.get(buffer))
            return false;

        // Garbled message
        if (tag != CheckSum)
            return false;

        // Garbled message
        if ((FIXCheckSums.sum(buffer, beginning, position - beginning + length) & 0xff)
                != checkSum.asInt())
            return false;

        buffer.position(position);

        return true;
    }

}

@@ -693,64 +693,8 @@
 X.*;
-%0Aimport static com.paritytrading.philadelphia.FIXTags.*;
 %0A%0Aim
@@ -2054,23 +2054,31 @@
         
-int tag
+boolean garbled
 ;%0A%0A     
@@ -2218,139 +2218,163 @@
     
-tag = FIXTags.get(buffer);%0A            if (tag == 0) %7B%0A                buffer.reset();%0A%0A                return false;%0A            %7D
+if (buffer.remaining() %3C 2)%0A                return false;%0A%0A            // Garbled message%0A            garbled = buffer.get() != '8' %7C%7C buffer.get() != '=';
 %0A%0A  
@@ -2575,26 +2575,15 @@
 if (
-tag != BeginString
+garbled
 )%0A  
@@ -2701,59 +2701,34 @@
     
-tag = FIXTags.get(buffer);%0A            if (tag == 0
+if (buffer.remaining() %3C 2
 ) %7B%0A
@@ -2797,32 +2797,130 @@
 %0A            %7D%0A%0A
+            // Garbled message%0A            garbled = buffer.get() != '9' %7C%7C buffer.get() != '=';%0A%0A
             // P
@@ -3071,60 +3071,19 @@
     
-// G
+if (g
 arbled
- message%0A            if (tag != BodyLength
 ) %7B%0A
@@ -3663,32 +3663,24 @@
             
-boolean 
 garbled = me
@@ -4101,112 +4101,91 @@
     
-// Garbled message%0A        int tag = FIXTags.get(buffer);%0A        if (tag == 0)%0A            return false
+boolean garbled = buffer.get() != '1' %7C%7C buffer.get() != '0' %7C%7C buffer.get() != '='
 ;%0A%0A 
@@ -4315,23 +4315,15 @@
 if (
-tag != CheckSum
+garbled
 )%0A  

Improve 'FIXMessageParser'