/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira.junit
 */
package net.ranides.assira.junit;

import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.TreeMap;
import java.util.regex.Pattern;
import net.ranides.assira.junit.mockup.TesterObjects;
import net.ranides.assira.junit.mockup.TesterObjects.*;
import net.ranides.assira.junit.mockup.Testers;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class TestContractRunnerTest {
    
    private static final List<String> EXPECTED = Arrays.asList(
        "ABC.findFirst",
        "ABC.findAll",
        "ForA2.exec",
        "ABC.close",
        "ABC.open",
        "ABC.read",
        "ABC.write",
        "ABC.next",

        "A.findFirst",
        "A.findAll",
        "ForA2.exec",

        "A.findFirst",
        "A.findAll",
        "ForA2.exec",
        "Abstract.close",
        "Abstract.open",
        "ForAbstractAB.exec"
    );
    
    private static final List<String> EXPECTED_A = Arrays.asList(
        "A.findFirst",
        "A.findAll"
    );
    
    private static final String EXPECTED_OUT = String.format(
        " - SUITE: net.ranides.assira.junit.mockup.TesterInterfaces$IntA [1] ForSupport.ignored - IGNORED%n" +
        " - SUITE: net.ranides.assira.junit.mockup.TesterInterfaces$IntA [2] ForSupport.otherMethod4Pattern - IGNORED%n" +
        " - SUITE: net.ranides.assira.junit.mockup.TesterInterfaces$IntA [3] ForSupport.run%n"
    );
    
    private static final List<String> EXPECTED_RES = Arrays.asList(
        "n=ranides c=33", 
        "v=text:33", 
        "n=null c=34", 
        "v=text:34", 
        "n=ot c=0",
        "v=text:0" 
    );

    private static final List<String> EXPECTED_FUNCTION = Arrays.asList(
        "ForFunction.function", 
        "ForFunction.list", 
        "ForFunction.supplier"
    );
	
	private static final List<String> EXPECTED_FUNCTION_I = Arrays.asList(
        "ForFunction.function", 
        "ForFunction.supplier=null"
    );

    @Test
    public void testRun() {
        TestContractRunner suite = new TestContractRunner(System.err)
            .append(new Testers.ForA1())
            .append(new Testers.ForB())
            .append(new Testers.ForC())
            .append(new Testers.ForAbstractAB())
            .append(new Testers.ForA2());

        TesterObjects.OUT.clear();
		suite.supplier(() -> new ObjectABC()).run();
		suite.supplier(() -> new ObjectA()).run();
		suite.supplier(() -> new AbstractAB(){}).run();
        assertEquals(EXPECTED, TesterObjects.OUT);
    }
    
    @Test
    public void testInvalid() {
        assertThrows(AssertionError.class, () ->
            new TestContractRunner(System.err).append(new Testers.Invalid())
        );
    }
    
    @Test
    public void testCorrect() {
        TestContractRunner suite = new TestContractRunner(System.err)
            .append(new Testers.Correct());

        TesterObjects.OUT.clear();
        suite.supplier(() -> new ObjectA()).run();
        assertEquals(EXPECTED_A, TesterObjects.OUT);
    }
    
    @Test
    public void testSupport() {
        TeeStream out = new TeeStream(System.err);
        TestContractRunner suite = new TestContractRunner(new PrintStream(out))
            .append(new Testers.ForSupport())
            .ignore("ForSupport.ignored")
            .ignore(Pattern.compile(".*Method4Pa.*"))
			.debug(true);

        TesterObjects.OUT.clear();
        suite.supplier(() -> new ObjectA()).run();
        assertEquals(EXPECTED_OUT, out.toString());
        assertEquals(EXPECTED_A, TesterObjects.OUT);
    }
    
    @Test
    public void testMapSupplier() {
        TesterObjects.OUT.clear();
        
        new TestContractRunner(System.err)
            .append(new Testers.ForMap())
            .supplier(() -> new TreeMap<>())
			.run();
        assertEquals(Arrays.asList("map"), TesterObjects.OUT);
    }
    
    @Test
    public void testWildcardSupplier() {
        assertThrows(AssertionError.class, ()->{
            new TestContractRunner(System.err)
                .append(new Testers.ForWildcard())
                .supplier(() -> new TreeMap<>())
				.run();
        });
    }
    
    @Test
    public void testFunction() {
		
        assertThrows(AssertionError.class, ()->{
            TesterObjects.OUT.clear();
            new TestContractRunner(System.err)
                .ignore("ForFunction.list")
                .append(new Testers.ForFunction())
                .function((args) -> new ArrayList<>())
                .run();
        });
            
        TesterObjects.OUT.clear();
        new TestContractRunner(System.err)
            .append(new Testers.ForFunction())
			.supplier(ArrayList::new)
            .function((Integer size) -> new ArrayList<>(size))
			.run();
        assertEquals(EXPECTED_FUNCTION, TesterObjects.OUT);
    }
    
    @Test
    public void testResources() {
        TesterObjects.OUT.clear();

        TestContractRunner suite = new TestContractRunner(System.err)
            .append(new Testers.ForResource1())
            .append(new Testers.ForResource2());
        suite
            .param("name", "ranides")
            .param("count!", 33)
            .supplier(() -> new Object())
			.run();
        suite
            .param("count!", 34)
            .supplier(() -> new Object())
			.run();
        suite
            .param("name", "ot")
            .supplier(() -> new Object())
			.run();
        
        assertEquals(EXPECTED_RES, TesterObjects.OUT);
        
//        System.out.printf("from = %s%n");
//        assertEquals("Hello world", tester.out());
    }
    
    private static class TeeStream extends FilterOutputStream {

        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        
        public TeeStream(OutputStream out) {
            super(out);
        }
        
        @Override
        public void write(int b) throws IOException {
            bytes.write(b);
            super.write(b);
        }
        
        @Override
        public String toString() {
            try {
                return bytes.toString(StandardCharsets.UTF_8.name());
            } catch (UnsupportedEncodingException ex) {
                throw TestUtils.rethrow(ex);
            }
        }
        
    }
    
}
