/*
 * WireSpider
 *
 * Copyright (c) 2015 kazyx
 *
 * This software is released under the MIT License.
 * http://opensource.org/licenses/mit-license.php
 */

package net.kazyx.wirespider;

import net.kazyx.wirespider.util.SelectionKeyUtil;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;

/**
 * Insecure TCP connection.
 */
class DefaultSession implements Session {
    private final SelectionKey mKey;
    private final SocketChannel mChannel;

    private static final int READ_BUFFER_SIZE = 1024 * 4;
    private final ByteBuffer mReadBuffer = ByteBuffer.allocateDirect(READ_BUFFER_SIZE);

    private static final int WRITE_BUFFER_SIZE = 1024 * 4;
    private final ByteBuffer mWriteBuffer = ByteBuffer.allocateDirect(WRITE_BUFFER_SIZE);

    private final LinkedList<ByteBuffer> mWriteQueue = new LinkedList<>();

    private Listener mListener;

    DefaultSession(SelectionKey key) {
        mKey = key;
        mChannel = (SocketChannel) key.channel();
    }

    @Override
    public void enqueueWrite(ByteBuffer data) throws IOException {
        synchronized (mWriteQueue) {
            if (!mKey.isValid()) {
                throw new IOException("SelectionKey is invalid");
            }
            mWriteQueue.addLast(data);
            if (mKey.interestOps() != (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) {
                SelectionKeyUtil.interestOps(mKey, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                mKey.selector().wakeup();
            }
        }
    }

    private ByteBuffer mRemaining = null;

    @Override
    public void onFlushReady() throws IOException {
        ByteBuffer data;

        synchronized (mWriteQueue) {
            while (mWriteBuffer.remaining() != 0 && (mRemaining != null || !mWriteQueue.isEmpty())) {
                if (mRemaining != null) {
                    data = mRemaining;
                    mRemaining = null;
                } else {
                    data = mWriteQueue.pollFirst();
                }

                if (data != null) {
                    if (mWriteBuffer.remaining() < data.remaining()) {
                        byte[] tmp = new byte[mWriteBuffer.remaining()];
                        data.get(tmp);
                        mRemaining = data;
                        mWriteBuffer.put(tmp);
                    } else {
                        mWriteBuffer.put(data);
                    }
                }
            }
        }

        mWriteBuffer.flip();
        mChannel.write(mWriteBuffer);
        mWriteBuffer.compact();

        synchronized (mWriteQueue) {
            if (mWriteBuffer.position() == 0 && mRemaining == null && mWriteQueue.isEmpty()) {
                SelectionKeyUtil.interestOps(mKey, SelectionKey.OP_READ);
            }
        }
    }

    @Override
    public void onReadReady() throws IOException {
        LinkedList<ByteBuffer> list = new LinkedList<>();

        while (true) {
            ByteBuffer buff = read();
            if (buff == null) {
                break;
            }
            list.add(buff);
        }

        if (mListener != null) {
            mListener.onAppDataReceived(list);
        }
    }

    @Override
    public void setListener(Listener listener) {
        mListener = listener;
        mListener.onConnected();
    }

    private ByteBuffer read() throws IOException {
        try {
            int length = mChannel.read(mReadBuffer);
            if (length == -1) {
                throw new IOException("EOF");
            } else if (length == 0) {
                return null;
            } else {
                mReadBuffer.flip();
                ByteBuffer buff = ByteBuffer.allocate(length);
                buff.put(mReadBuffer);
                buff.flip();
                return buff;
            }
        } finally {
            mReadBuffer.clear();
        }
    }

    @Override
    public void close() throws IOException {
        mChannel.close();
    }
}

@@ -397,16 +397,65 @@
 kedList;
+%0Aimport java.util.concurrent.LinkedBlockingQueue;
 %0A%0A/**%0A *
@@ -923,28 +923,37 @@
 final Linked
-List
+BlockingQueue
 %3CByteBuffer%3E
@@ -969,38 +969,95 @@
 eue = new Linked
-List%3C%3E
+BlockingQueue%3C%3E();%0A%0A    private final Object mLock = new Object
 ();%0A%0A    private
@@ -1279,49 +1279,8 @@
 n %7B%0A
-        synchronized (mWriteQueue) %7B%0A    
     
@@ -1302,28 +1302,24 @@
 sValid()) %7B%0A
-    
             
@@ -1372,34 +1372,26 @@
 %22);%0A        
-    %7D%0A    
+%7D%0A
         mWri
@@ -1405,19 +1405,47 @@
 .add
-Last
 (data);
+%0A%0A        synchronized (mLock) %7B
 %0A   
@@ -1839,49 +1839,8 @@
 a;%0A%0A
-        synchronized (mWriteQueue) %7B%0A    
     
@@ -1941,28 +1941,24 @@
             
-    
 if (mRemaini
@@ -1979,36 +1979,32 @@
                 
-  
-  
 data = mRemainin
@@ -2006,20 +2006,16 @@
 aining;%0A
-    
         
@@ -2045,36 +2045,32 @@
 ll;%0A            
-  
-  
 %7D else %7B%0A       
@@ -2054,36 +2054,32 @@
        %7D else %7B%0A
-    
                 
@@ -2105,21 +2105,12 @@
 poll
-First();%0A    
+();%0A
     
@@ -2128,28 +2128,24 @@
             
-    
 if (data != 
@@ -2144,36 +2144,32 @@
 data != null) %7B%0A
-    
                 
@@ -2239,20 +2239,16 @@
         
-    
 byte%5B%5D t
@@ -2304,28 +2304,24 @@
             
-    
 data.get(tmp
@@ -2315,36 +2315,32 @@
  data.get(tmp);%0A
-    
                 
@@ -2374,36 +2374,32 @@
                 
-   
- 
 mWriteBuffer.put
@@ -2413,36 +2413,32 @@
                 
-    
 %7D else %7B%0A       
@@ -2442,36 +2442,32 @@
                 
-   
- 
 mWriteBuffer.put
@@ -2478,30 +2478,8 @@
 a);%0A
-                    %7D%0A
     
@@ -2640,26 +2640,20 @@
 nized (m
-WriteQueue
+Lock
 ) %7B%0A    

Optimize sync lock