//
//  ========================================================================
//  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.server;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.EOFException;
import java.io.IOException;
import java.util.Queue;
import java.util.concurrent.TimeoutException;

import javax.servlet.ReadListener;

import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ConcurrentArrayQueue;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AdvancedRunner.class)
public class HttpInputTest
{
    Queue<String> _history = new ConcurrentArrayQueue<String>()
            {
                @Override
                public boolean add(String s)
                {
                    //System.err.println("history: "+s);
                    return super.add(s);
                }
            };
    Queue<String> _fillAndParseSimulate = new ConcurrentArrayQueue<>();
    HttpInput _in;
    
    ReadListener _listener = new ReadListener()
    {
        @Override
        public void onError(Throwable t)
        {
            _history.add("onError:"+t);
        }
        
        @Override
        public void onDataAvailable() throws IOException
        {
            _history.add("onDataAvailable");
        }
        
        @Override
        public void onAllDataRead() throws IOException
        {
            _history.add("onAllDataRead");
        }
    };
    
    public class TContent extends HttpInput.Content
    {
        private final String _content;
        public TContent(String content)
        {
            super(BufferUtil.toBuffer(content));
            _content=content;
        }
        
        @Override
        public void succeeded()
        {
            _history.add("Content succeeded "+_content);
            super.succeeded();
        }
        
        @Override
        public void failed(Throwable x)
        {
            _history.add("Content failed "+_content);
            super.failed(x);
        }
    }
    
    @Before
    public void before()
    {
        _in=new HttpInput(new HttpChannelState(new HttpChannel(null,new HttpConfiguration(),null,null)
        {
            @Override
            public void asyncReadFillInterested()
            {
                _history.add("asyncReadFillInterested");
            }
        })
        {
            
            @Override
            public void onReadUnready()
            {
                _history.add("unready");
                super.onReadUnready();
            }

            @Override
            public boolean onReadPossible()
            {
                _history.add("onReadPossible");
                return super.onReadPossible();
            }
            
            @Override
            public boolean onReadReady()
            {
                _history.add("ready");
                return super.onReadReady();
            }
        })
        {
            @Override
            protected void produceContent() throws IOException
            {
                _history.add("produceContent "+_fillAndParseSimulate.size()); 
                
                for (String s=_fillAndParseSimulate.poll();s!=null;s=_fillAndParseSimulate.poll())
                {
                    if ("_EOF_".equals(s))
                        _in.eof();
                    else
                        _in.addContent(new TContent(s));
                }
            }

            @Override
            protected void blockForContent() throws IOException
            {
                _history.add("blockForContent"); 
                super.blockForContent();
            }
        };
    }
    
    @After
    public void after()
    {
        assertThat(_history.poll(),nullValue());
    }
    
    @Test
    public void testEmpty() throws Exception
    {
        assertThat(_in.available(),equalTo(0));
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.isReady(),equalTo(true));
        assertThat(_history.poll(),nullValue());
    }

    @Test
    public void testRead() throws Exception
    {
        _in.addContent(new TContent("AB"));
        _in.addContent(new TContent("CD"));
        _fillAndParseSimulate.offer("EF");
        _fillAndParseSimulate.offer("GH");
        assertThat(_in.available(),equalTo(2));
        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.isReady(),equalTo(true));

        assertThat(_in.getContentConsumed(),equalTo(0L));
        assertThat(_in.read(),equalTo((int)'A'));
        assertThat(_in.getContentConsumed(),equalTo(1L));
        assertThat(_in.read(),equalTo((int)'B'));
        assertThat(_in.getContentConsumed(),equalTo(2L));
        
        assertThat(_history.poll(),equalTo("Content succeeded AB"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.read(),equalTo((int)'C'));
        assertThat(_in.read(),equalTo((int)'D'));
        
        assertThat(_history.poll(),equalTo("Content succeeded CD"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.read(),equalTo((int)'E'));
        assertThat(_in.read(),equalTo((int)'F'));

        assertThat(_history.poll(),equalTo("produceContent 2"));
        assertThat(_history.poll(),equalTo("Content succeeded EF"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.read(),equalTo((int)'G'));
        assertThat(_in.read(),equalTo((int)'H'));
        
        assertThat(_history.poll(),equalTo("Content succeeded GH"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.getContentConsumed(),equalTo(8L));
        
        assertThat(_history.poll(),nullValue());
    }


    @Test
    public void testReRead() throws Exception
    {
        _in.addContent(new TContent("AB"));
        _in.addContent(new TContent("CD"));
        _fillAndParseSimulate.offer("EF");
        _fillAndParseSimulate.offer("GH");
        assertThat(_in.available(),equalTo(2));
        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.isReady(),equalTo(true));

        assertThat(_in.getContentConsumed(),equalTo(0L));
        assertThat(_in.read(),equalTo((int)'A'));
        assertThat(_in.getContentConsumed(),equalTo(1L));
        assertThat(_in.read(),equalTo((int)'B'));
        assertThat(_in.getContentConsumed(),equalTo(2L));
        
        assertThat(_history.poll(),equalTo("Content succeeded AB"));
        assertThat(_history.poll(),nullValue());
        assertThat(_in.read(),equalTo((int)'C'));
        assertThat(_in.read(),equalTo((int)'D'));
        
        assertThat(_history.poll(),equalTo("Content succeeded CD"));
        assertThat(_history.poll(),nullValue());
        assertThat(_in.read(),equalTo((int)'E'));
        
        _in.prependContent(new HttpInput.Content(BufferUtil.toBuffer("abcde")));

        assertThat(_in.available(),equalTo(5));
        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.isReady(),equalTo(true));

        assertThat(_in.getContentConsumed(),equalTo(0L));
        assertThat(_in.read(),equalTo((int)'a'));
        assertThat(_in.getContentConsumed(),equalTo(1L));
        assertThat(_in.read(),equalTo((int)'b'));
        assertThat(_in.getContentConsumed(),equalTo(2L));
        assertThat(_in.read(),equalTo((int)'c'));
        assertThat(_in.read(),equalTo((int)'d'));        
        assertThat(_in.read(),equalTo((int)'e'));
        
        
        
        assertThat(_in.read(),equalTo((int)'F'));

        assertThat(_history.poll(),equalTo("produceContent 2"));
        assertThat(_history.poll(),equalTo("Content succeeded EF"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.read(),equalTo((int)'G'));
        assertThat(_in.read(),equalTo((int)'H'));
        
        assertThat(_history.poll(),equalTo("Content succeeded GH"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.getContentConsumed(),equalTo(8L));
        
        assertThat(_history.poll(),nullValue());
    }
    
    @Test
    public void testBlockingRead() throws Exception
    {
        new Thread()
        {
            public void run() 
            {
                try
                {
                    Thread.sleep(500);
                    _in.addContent(new TContent("AB"));
                }
                catch(Throwable th)
                {
                    th.printStackTrace();
                }
            }
        }.start();
        
        assertThat(_in.read(),equalTo((int)'A'));
        
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("blockForContent"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.read(),equalTo((int)'B'));
        
        assertThat(_history.poll(),equalTo("Content succeeded AB"));
        assertThat(_history.poll(),nullValue());
    }

    @Test
    public void testReadEOF() throws Exception
    {
        _in.addContent(new TContent("AB"));
        _in.addContent(new TContent("CD"));
        _in.eof();

        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.available(),equalTo(2));
        assertThat(_in.isFinished(),equalTo(false));

        assertThat(_in.read(),equalTo((int)'A'));
        assertThat(_in.read(),equalTo((int)'B'));
        assertThat(_history.poll(),equalTo("Content succeeded AB"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.read(),equalTo((int)'C'));
        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.read(),equalTo((int)'D'));
        assertThat(_history.poll(),equalTo("Content succeeded CD"));
        assertThat(_history.poll(),nullValue());
        assertThat(_in.isFinished(),equalTo(false));
        
        assertThat(_in.read(),equalTo(-1));
        assertThat(_in.isFinished(),equalTo(true));
        
        assertThat(_history.poll(),nullValue());
    }

    @Test
    public void testReadEarlyEOF() throws Exception
    {
        _in.addContent(new TContent("AB"));
        _in.addContent(new TContent("CD"));
        _in.earlyEOF();

        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.available(),equalTo(2));
        assertThat(_in.isFinished(),equalTo(false));

        assertThat(_in.read(),equalTo((int)'A'));
        assertThat(_in.read(),equalTo((int)'B'));
        
        assertThat(_in.read(),equalTo((int)'C'));
        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.read(),equalTo((int)'D'));
        
        try
        {
            _in.read();
            fail();
        }
        catch(EOFException eof)
        {
            assertThat(_in.isFinished(),equalTo(true));
        }

        assertThat(_history.poll(),equalTo("Content succeeded AB"));
        assertThat(_history.poll(),equalTo("Content succeeded CD"));
        assertThat(_history.poll(),nullValue());
    }
    
    @Test
    public void testBlockingEOF() throws Exception
    {
        new Thread()
        {
            public void run() 
            {
                try
                {
                    Thread.sleep(500);
                    _in.eof();
                }
                catch(Throwable th)
                {
                    th.printStackTrace();
                }
            }
        }.start();

        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.read(),equalTo(-1));
        assertThat(_in.isFinished(),equalTo(true));
        
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("blockForContent"));
        assertThat(_history.poll(),nullValue());   
    }
    
    @Test
    public void testAsyncEmpty() throws Exception
    {
        _in.setReadListener(_listener);
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
        
        _in.run();
        assertThat(_history.poll(),equalTo("onDataAvailable"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.isReady(),equalTo(false));
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.isReady(),equalTo(false));
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
    }
    

    @Test
    public void testAsyncRead() throws Exception
    {
        _in.setReadListener(_listener);
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
        
        _in.run();
        assertThat(_history.poll(),equalTo("onDataAvailable"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.isReady(),equalTo(false));
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
        
        _in.addContent(new TContent("AB"));
        _fillAndParseSimulate.add("CD");
        
        assertThat(_history.poll(),equalTo("onReadPossible"));
        assertThat(_history.poll(),nullValue());
        _in.run();
        assertThat(_history.poll(),equalTo("onDataAvailable"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.isReady(),equalTo(true));
        assertThat(_in.read(),equalTo((int)'A'));
        
        assertThat(_in.isReady(),equalTo(true));
        assertThat(_in.read(),equalTo((int)'B'));

        assertThat(_history.poll(),equalTo("Content succeeded AB"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.isReady(),equalTo(true));
        assertThat(_history.poll(),equalTo("produceContent 1"));
        assertThat(_history.poll(),equalTo("onReadPossible"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.read(),equalTo((int)'C'));
        
        assertThat(_in.isReady(),equalTo(true));
        assertThat(_in.read(),equalTo((int)'D'));
        assertThat(_history.poll(),equalTo("Content succeeded CD"));
        assertThat(_history.poll(),nullValue());
        

        assertThat(_in.isReady(),equalTo(false));
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
    }
    
    @Test
    public void testAsyncEOF() throws Exception
    {
        _in.setReadListener(_listener);
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());

        _in.run();
        assertThat(_history.poll(),equalTo("onDataAvailable"));
        assertThat(_history.poll(),nullValue());

        _in.eof();
        assertThat(_in.isReady(),equalTo(true));
        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_history.poll(),equalTo("onReadPossible"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.read(),equalTo(-1));
        assertThat(_in.isFinished(),equalTo(true));
        assertThat(_history.poll(),equalTo("ready"));
        assertThat(_history.poll(),nullValue());
    }
    
    @Test
    public void testAsyncReadEOF() throws Exception
    {
        _in.setReadListener(_listener);
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
        
        _in.run();
        assertThat(_history.poll(),equalTo("onDataAvailable"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.isReady(),equalTo(false));
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
        
        _in.addContent(new TContent("AB"));
        _fillAndParseSimulate.add("_EOF_");
        
        assertThat(_history.poll(),equalTo("onReadPossible"));
        assertThat(_history.poll(),nullValue());
        
        _in.run();
        assertThat(_history.poll(),equalTo("onDataAvailable"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.isReady(),equalTo(true));
        assertThat(_in.read(),equalTo((int)'A'));
        
        assertThat(_in.isReady(),equalTo(true));
        assertThat(_in.read(),equalTo((int)'B'));

        assertThat(_history.poll(),equalTo("Content succeeded AB"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.isReady(),equalTo(true));
        assertThat(_history.poll(),equalTo("produceContent 1"));
        assertThat(_history.poll(),equalTo("onReadPossible"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.isFinished(),equalTo(false));
        assertThat(_in.read(),equalTo(-1));        
        assertThat(_in.isFinished(),equalTo(true));
        assertThat(_history.poll(),equalTo("ready"));
        assertThat(_history.poll(),nullValue());
        
        assertThat(_in.isReady(),equalTo(true));
        assertThat(_history.poll(),nullValue());

    }

    
    @Test
    public void testAsyncError() throws Exception
    {
        _in.setReadListener(_listener);
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
        _in.run();
        assertThat(_history.poll(),equalTo("onDataAvailable"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.isReady(),equalTo(false));
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),equalTo("unready"));
        assertThat(_history.poll(),nullValue());
        
        _in.failed(new TimeoutException());
        assertThat(_history.poll(),equalTo("onReadPossible"));
        assertThat(_history.poll(),nullValue());
        
        _in.run();
        assertThat(_in.isFinished(),equalTo(true));
        assertThat(_history.poll(),equalTo("onError:java.util.concurrent.TimeoutException"));
        assertThat(_history.poll(),nullValue());

        assertThat(_in.isReady(),equalTo(true));
        try
        {
            _in.read();
            fail();
        }
        catch(IOException e)
        {
            assertThat(e.getCause(),Matchers.instanceOf(TimeoutException.class));
            assertThat(_in.isFinished(),equalTo(true));   
        }

        assertThat(_history.poll(),nullValue());
    }
    

    @Test
    public void testRecycle() throws Exception
    {
        testAsyncRead();
        _in.recycle();
        testAsyncRead();
        _in.recycle();
        testReadEOF();
    }
    
    @Test
    public void testConsumeAll() throws Exception
    {
        _in.addContent(new TContent("AB"));
        _in.addContent(new TContent("CD"));
        _fillAndParseSimulate.offer("EF");
        _fillAndParseSimulate.offer("GH");
        assertThat(_in.read(),equalTo((int)'A'));
        
        assertFalse(_in.consumeAll());
        assertThat(_in.getContentConsumed(),equalTo(8L));

        assertThat(_history.poll(),equalTo("Content succeeded AB"));
        assertThat(_history.poll(),equalTo("Content succeeded CD"));
        assertThat(_history.poll(),equalTo("produceContent 2"));
        assertThat(_history.poll(),equalTo("Content succeeded EF"));
        assertThat(_history.poll(),equalTo("Content succeeded GH"));
        assertThat(_history.poll(),equalTo("produceContent 0"));
        assertThat(_history.poll(),nullValue());
    }
    
    @Test
    public void testConsumeAllEOF() throws Exception
    {
        _in.addContent(new TContent("AB"));
        _in.addContent(new TContent("CD"));
        _fillAndParseSimulate.offer("EF");
        _fillAndParseSimulate.offer("GH");
        _fillAndParseSimulate.offer("_EOF_");
        assertThat(_in.read(),equalTo((int)'A'));
        
        assertTrue(_in.consumeAll());
        assertThat(_in.getContentConsumed(),equalTo(8L));

        assertThat(_history.poll(),equalTo("Content succeeded AB"));
        assertThat(_history.poll(),equalTo("Content succeeded CD"));
        assertThat(_history.poll(),equalTo("produceContent 3"));
        assertThat(_history.poll(),equalTo("Content succeeded EF"));
        assertThat(_history.poll(),equalTo("Content succeeded GH"));
        assertThat(_history.poll(),nullValue());
    }
}
