/*
    Copywrite 2013-2015 Will Winder

    This file is part of Universal Gcode Sender (UGS).

    UGS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    UGS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with UGS.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.willwinder.universalgcodesender;

import com.willwinder.universalgcodesender.utils.CommUtils;
import com.willwinder.universalgcodesender.mockobjects.MockConnection;
import com.willwinder.universalgcodesender.mockobjects.MockGrbl;
import com.willwinder.universalgcodesender.types.GcodeCommand;
import java.io.IOException;
import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingDeque;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;

/**
 *
 * @author wwinder
 */
public class GrblCommunicatorTest {
    MockGrbl mg;
    LinkedBlockingDeque<String> cb;
    LinkedBlockingDeque<GcodeCommand> asl;
    
    public GrblCommunicatorTest() {
    }

    @Before
    public void setUp() {
        this.mg = new MockGrbl();
        this.cb = new LinkedBlockingDeque<>();
        this.asl = new LinkedBlockingDeque<>();
    }

    /**
     * Test of openCommPort method, of class GrblCommunicator.
     */
    @Test
    public void testOpenCommPort() throws Exception {
        System.out.println("openCommPort");
        System.out.println("-not testing RXTX.");
    }

    /**
     * Test of closeCommPort method, of class GrblCommunicator.
     */
    @Test
    public void testCloseCommPort() {
        System.out.println("closeCommPort");
        System.out.println("-not testing RXTX.");
    }

    /**
     * Test of queueStringForComm method, of class GrblCommunicator.
     */
    @Test
    public void testQueueStringForComm() throws Exception {
        
        System.out.println("queueStringForComm");
        String input = "someCommand";
        MockConnection mc = new MockConnection(mg.in, mg.out);
        GrblCommunicator instance = new GrblCommunicator(cb, asl, mc);
        
        try {
            instance.queueStringForComm(input);
            // The cb preloads commands so the size represents queued commands.
            assertEquals(1, cb.size());
            
            // Test that instance adds newline to improperly formed command.
            String next = cb.peek();
            assertEquals(input, cb.peek());
            
            instance.queueStringForComm(input);
            instance.queueStringForComm(input);
            
            // Test that instance continues to queue inputs.
            assertEquals(3, cb.size());
            
            input = "someCommand";
            cb = new LinkedBlockingDeque<>();
            mc = new MockConnection(mg.in, mg.out);
            instance = new GrblCommunicator(cb, asl, mc);

            instance.queueStringForComm(input);
            // Test that instance doesn't add superfluous newlines.
            assertEquals(input, cb.peek());

        } catch (Exception e) {
            fail("queueStringForComm threw an exception: "+e.getMessage());
        }
    }

    
    /**
     * Test of sendStringToComm method, of class GrblCommunicator.
     */
    /* This function is private...
    @Test
    public void testSendStringToComm() {
        System.out.println("sendStringToComm");
        GrblCommunicator instance = new GrblCommunicator(mg.in, mg.out, cb, asl);

        
        String command = "someCommand";
        instance.sendStringToComm(command);
        
        // Make sure the string made it to GRBL.
        assertEquals(command, mg.readStringFromGrblBuffer());
    }
    */
    /**
     * Test of sendByteImmediately method, of class GrblCommunicator.
     */
    @Test
    public void testSendByteImmediately() {
        System.out.println("sendByteImmediately");
        MockConnection mc = new MockConnection(mg.in, mg.out);
        GrblCommunicator instance = new GrblCommunicator(cb, asl, mc);

        // Ctrl-C is a common byte to send immediately.
        byte b = 0x18;
        
        try {
            instance.sendByteImmediately(b);
        } catch (Exception e) {
            fail("sendByteImmediately threw an exception: " + e.getMessage());
        }

        // Make sure the string made it to GRBL.
        assertEquals(b, mg.readByteFromGrblBuffer());

        // Test my readByteFromGrblBuffer command (should be empty).
        assertEquals(0x0, mg.readByteFromGrblBuffer());
        
        try {
            // Buffer multiple bytes
            instance.sendByteImmediately((byte)0x18);
            instance.sendByteImmediately((byte)0x19);
            instance.sendByteImmediately((byte)0x20);
            instance.sendByteImmediately((byte)0x21);
        } catch (Exception e) {
            fail("sendByteImmediately threw an exception: " + e.getMessage());
        }

        assertEquals(0x18, mg.readByteFromGrblBuffer());
        assertEquals(0x19, mg.readByteFromGrblBuffer());
        assertEquals(0x20, mg.readByteFromGrblBuffer());
        assertEquals(0x21, mg.readByteFromGrblBuffer());
        
        // Test my readByteFromGrblBuffer command (should be empty).
        assertEquals(0x0, mg.readByteFromGrblBuffer());
    }

    /**
     * Test of areActiveCommands method, of class GrblCommunicator.
     */
    @Test
    public void testAreActiveCommands() {
        System.out.println("areActiveCommands");
        MockConnection mc = new MockConnection(mg.in, mg.out);
        GrblCommunicator instance = new GrblCommunicator(cb, asl, mc);
        
        boolean expResult = false;
        boolean result = instance.areActiveCommands();
        
        // Empty case.
        assertEquals(expResult, result);
        
        // Add a command and stream it (activate it).
        instance.queueStringForComm("command one");
        instance.streamCommands();
        expResult = true;
        result = instance.areActiveCommands();
        assertEquals(expResult, result);
        
        // Add another command and stream it.
        instance.queueStringForComm("command one");
        instance.streamCommands();
        expResult = true;
        result = instance.areActiveCommands();
        assertEquals(expResult, result);
        
        // Send result to communicator.
        instance.responseMessage("ok");
        instance.responseMessage("ok");
                
        expResult = false;
        result = instance.areActiveCommands();
        assertEquals(expResult, result);
    }

    /**
     * Test of sendStringToComm method, of class GrblCommunicator.
     */
    @Test
    public void testStreamCommands() {
        System.out.println("streamCommands");
        MockConnection mc = new MockConnection(mg.in, mg.out);
        GrblCommunicator instance = new GrblCommunicator(cb, asl, mc);
        String term = "\n";
        String thirtyNineCharString = "thirty-nine character command here.....";

        boolean result;
        boolean expResult;
        
        // Make sure CommUtil is still an overly cautious jerk.
        LinkedList<GcodeCommand> l = new LinkedList<>();
        l.add(new GcodeCommand("12characters"));
        assertEquals(13, CommUtils.getSizeOfBuffer(l));

        // Make sure GrblUtils hasn't updated RX buffer size.
        assertEquals(123, GrblUtils.GRBL_RX_BUFFER_SIZE);

        // Add a bunch of commands so that the buffer is full.
        // 39*3 + 3 newlines + 3 CommUtils caution  = 123 == buffer size.
        instance.queueStringForComm(thirtyNineCharString);
        instance.queueStringForComm(thirtyNineCharString);
        instance.queueStringForComm(thirtyNineCharString);
        
        // Stream them so that there are active commands.
        instance.streamCommands();
        expResult = true;
        result = instance.areActiveCommands();
        assertEquals(expResult, result);

        // Add another command and stream it so that something is queued.
        instance.queueStringForComm(thirtyNineCharString);
        instance.streamCommands();
        expResult = true;
        result = instance.areActiveCommands();
        assertEquals(expResult, result);

        // Check with MockGrbl to verify the fourth command wasn't sent.
        String output = mg.readStringFromGrblBuffer();
        String goal = thirtyNineCharString+term+thirtyNineCharString+term+thirtyNineCharString+term;
        assertEquals(goal, output);
        
        // Make room for the next command.
        mc.sendResponse("ok");
        
        // Send it.
        instance.streamCommands();
        
        // Make sure the queued command was sent.
        output = mg.readStringFromGrblBuffer();
        assertEquals(thirtyNineCharString+term, output);
  
        // Wrap up.
        mc.sendResponse("ok");
        mc.sendResponse("ok");
        mc.sendResponse("ok");

        expResult = false;
        result = instance.areActiveCommands();
        assertEquals(expResult, result);
    }
    
    /**
     * Test of pauseSend method, of class GrblCommunicator.
     */
    @Test
    public void testPauseSendAndResumeSend() {
        System.out.println("pauseSend");
        MockConnection mc = new MockConnection(mg.in, mg.out);
        GrblCommunicator instance = new GrblCommunicator(cb, asl, mc);
        String twentyCharString = "twenty characters...";
        String grblReceiveString;
        String arr[];
        int expectedInt;
        
        // Queue a bunch of commands
        for (int i=0; i < 30; i++) {
            instance.queueStringForComm(twentyCharString);
        }
        
        // Fill initial buffer, then pause.
        instance.streamCommands();
        instance.pauseSend();

        // Check that the correct number of commands were buffered (even though
        // that isn't pause/resume).
        grblReceiveString = mg.readStringFromGrblBuffer();
        arr = grblReceiveString.split("\n");
        expectedInt = GrblUtils.GRBL_RX_BUFFER_SIZE / (twentyCharString.length()+1);
        assertEquals(expectedInt, arr.length);

        for (String arr1 : arr) {
            mc.sendResponse("ok");
        }

        // Make sure we don't stream anymore.
        instance.streamCommands();
        grblReceiveString = mg.readStringFromGrblBuffer();
        expectedInt = 0;
        assertEquals(expectedInt, grblReceiveString.length());

        instance.resumeSend();
        
        // Make sure streaming continued after resuming.
        instance.streamCommands();
        grblReceiveString = mg.readStringFromGrblBuffer();
        arr = grblReceiveString.split("\n");
        expectedInt = GrblUtils.GRBL_RX_BUFFER_SIZE / (twentyCharString.length()+1);
        assertEquals(expectedInt, arr.length);
    }

    /**
     * Test of cancelSend method, of class GrblCommunicator.
     */
    @Test
    public void testCancelSend() {
        System.out.println("cancelSend");
        MockConnection mc = new MockConnection(mg.in, mg.out);
        GrblCommunicator instance = new GrblCommunicator(cb, asl, mc);
        String twentyCharString = "twenty characters...";
        String grblReceiveString;
        String arr[];
        int expectedInt;
        Boolean expectedBool;
        
        // 1.
        // Queue some commands, but cancel before sending them.
        for (int i=0; i < 30; i++) {
            instance.queueStringForComm(twentyCharString);
        }
        instance.cancelSend();

        // try streaming them and make sure nothing is received.
        instance.streamCommands();
        grblReceiveString = mg.readStringFromGrblBuffer();
        expectedInt = 0;
        assertEquals(expectedInt, grblReceiveString.length());
        
        // 2.
        // We can't un-send things to GRBL, so make sure we still acknowledge
        // that there are active commands.
        for (int i=0; i < 30; i++) {
            instance.queueStringForComm(twentyCharString);
        }
        instance.streamCommands();
        instance.cancelSend();
        // Verify that there are several active commands.
        expectedBool = true;
        assertEquals(expectedBool, instance.areActiveCommands());
        
        grblReceiveString = mg.readStringFromGrblBuffer();
        arr = grblReceiveString.split("\n");
        expectedInt = GrblUtils.GRBL_RX_BUFFER_SIZE / (twentyCharString.length()+1);
        assertEquals(expectedInt, arr.length);

        for (String arr1 : arr) {
            mc.sendResponse("ok");
        }
            
        // Make sure canceled commands are not sent.
        instance.streamCommands();
        grblReceiveString = mg.readStringFromGrblBuffer();
        expectedInt = 0;
        assertEquals(expectedInt, grblReceiveString.length());
    }

    /**
     * Test of softReset method, of class GrblCommunicator.
     */
    @Test
    public void testSoftReset() {
        System.out.println("softReset");
        MockConnection mc = new MockConnection(mg.in, mg.out);
        GrblCommunicator instance = new GrblCommunicator(cb, asl, mc);
        String twentyCharString = "twenty characters...";
        String grblReceiveString;
        String arr[];
        int expectedInt;
        Boolean expectedBool;
        
        // This is essentially a cancel that "un sends" commands.
        // Send some commands, make them active, then soft-reset to reset them.
        for (int i=0; i < 30; i++) {
            instance.queueStringForComm(twentyCharString);
        }
        instance.streamCommands();
        instance.softReset();
        // Verify that there are several active commands.
        expectedBool = false;
        assertEquals(expectedBool, instance.areActiveCommands());
    }

    /**
     * Test of sendStringToComm method, of class GrblCommunicator.
     */
    @Test
    public void testErrorResponse() {
        System.out.println("streamCommands");
        MockConnection mc = new MockConnection(mg.in, mg.out);
        GrblCommunicator instance = new GrblCommunicator(cb, asl, mc);
        instance.setSingleStepMode(true);
        String term = "\n";
        String thirtyNineCharString = "thirty-nine character command here.....";

        boolean result;
        boolean expResult;
        
        // Make sure CommUtil is still an overly cautious jerk.
        LinkedList<GcodeCommand> l = new LinkedList<>();
        l.add(new GcodeCommand("12characters"));

        // Add a bunch of commands so that the buffer is full.
        // 39*3 + 3 newlines + 3 CommUtils caution  = 123 == buffer size.
        instance.queueStringForComm(thirtyNineCharString);
        instance.queueStringForComm(thirtyNineCharString);
        instance.queueStringForComm(thirtyNineCharString);
        instance.queueStringForComm(thirtyNineCharString);

        // First command activated, next loaded (not tested) and two queued.
        instance.streamCommands();
        assertEquals(1, asl.size());
        assertEquals(2, cb.size());

        // Complete one command, next becomes active, one left in the queue.
        mc.sendResponse("ok");
        assertEquals(1, asl.size());
        assertEquals(1, cb.size());

        // After an error the active command completes but queue is not changed.
        mc.sendResponse("error");
        assertEquals(0, asl.size());
        assertEquals(1, cb.size());
    }
}
