/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.netbeans.core.output2;

import java.awt.Color;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.junit.NbTestCase;
import org.netbeans.junit.RandomlyFails;
import org.openide.util.Exceptions;
import org.openide.windows.OutputEvent;
import org.openide.windows.OutputListener;

/** Tests the OutWriter class
 *
 * @author Tim Boudreau
 */
public class OutWriterTest extends NbTestCase {

    public OutWriterTest(String testName) {
        super(testName);
    }

    public void testPositionOfLine() throws UnsupportedEncodingException {
        System.out.println("testPositionOfLine");

        OutWriter ow = new OutWriter ();
        
        String first = "This is the first string";
        String second = "This is the second string, ain't it?";
        String third = "This is the third string";
        
        ow.println(first);
        
        ow.println (second);
        
        ow.println (third);
        
        int pos = ow.getLines().getLineStart(0);
        
        assertTrue ("First line position should be 0 but is " + pos, pos == 0);
        
        int expectedPosition = first.length() + OutWriter.LINE_SEPARATOR.length();
        pos = ow.getLines().getLineStart(1);
        
        assertTrue ("Second line position should be length of first (" + first.length() + ") + line " +
            "separator length (" + OutWriter.LINE_SEPARATOR.length() + "), which should be " +
            expectedPosition + " but is " + pos, 
            pos == expectedPosition);
         
        
        pos = ow.getLines().getLineStart (2);
        int targetPos = first.length() + second.length() + 2 * OutWriter.LINE_SEPARATOR.length();
        
        assertTrue ("Third line position should be " + targetPos + " but is " + pos, pos == targetPos);
    }
    
    
    public void testPosition() throws UnsupportedEncodingException {
        System.out.println("testPosition");
        
        OutWriter ow = new OutWriter();

        
        String first = "This is the first string";
        String second ="This is the second string";
        String third = "This is the third string";
        
        assertTrue (ow.getLines().getLineCount() == 1);
        
        ow.println(first);
        
        assertTrue (ow.getLines().getLineCount() == 2);
        
        ow.println (second);
        
        assertTrue (ow.getLines().getLineCount() == 3);

        int targetLength = first.length() + second.length() + 2 * OutWriter.LINE_SEPARATOR.length();

        assertTrue ( 
            "After printing strings with length " + first.length() + " and " + 
            second.length() + " outfile position should be " + targetLength +
            " not " + ow.getLines().getCharCount(),
            ow.getLines().getCharCount() == targetLength);
        
        ow.println (third);
        
        targetLength = first.length() + second.length() + third.length() + 
            (3 *  OutWriter.LINE_SEPARATOR.length());
        
        assertTrue ("Length should be " + targetLength + " but position is "
            + ow.getLines().getCharCount(), targetLength == ow.getLines().getCharCount());
    }
    
    public void testLine() throws UnsupportedEncodingException {
        System.out.println("testLine");
        
        
        OutWriter ow = new OutWriter ();
        
        String first = "This is the first string";
        String second = "This is the second string, ain't it?";
        String third = "This is the third string";
        
        ow.println(first);
        
        ow.println (second);
        
        ow.println (third);
        
        assertTrue ("After writing 3 lines, linecount should be 4, not " + 
            ow.getLines().getLineCount(), ow.getLines().getLineCount() == 4);
        
        String firstBack = null;
        String secondBack = null;
        String thirdBack = null;
        try {
            firstBack = ow.getLines().getLine(0);
            secondBack = ow.getLines().getLine(1);
            thirdBack = ow.getLines().getLine(2);
        } catch (IOException ioe) {
            ioe.printStackTrace();
            fail (ioe.getMessage());
        }
        String firstExpected = first + OutWriter.LINE_SEPARATOR;
        String secondExpected = second + OutWriter.LINE_SEPARATOR;
        String thirdExpected = third + OutWriter.LINE_SEPARATOR;
        
        assertEquals("First string should be \"" + firstExpected + "\" but was \"" + firstBack + "\"",
            firstBack, firstExpected);
        
        assertEquals("Second string should be \"" + secondExpected + "\" but was \"" + secondBack + "\"",
            secondBack, secondExpected);

        assertEquals("Third string should be \"" + thirdExpected + "\" but was \"" + thirdBack + "\"",
            thirdBack, thirdExpected);
        
    }
     
    public void testLineForPosition() {
        System.out.println("testLineForPosition");
        
        
        OutWriter ow = new OutWriter ();
        
        String first = "This is the first string";
        String second = "This is the second string, ain't it?";
        String third = "This is the third string";
        
        ow.println(first);
        
        ow.println (second);
        
        ow.println (third);
        
        int line = ow.getLines().getLineAt (first.length() / 2);
        
        assertTrue ("Position halfway through first line should map to line 0," +
            " not " + line,
            line == 0);
        
        line = ow.getLines().getLineAt (first.length() + OutWriter.LINE_SEPARATOR.length() +
            (second.length() / 2));
        
        assertTrue ("Position halfway through line 1 should map to line 1, not " +
            line,
            line == 1);
        
        //XXX do some more tests here for very large buffers, to ensure no
        //off-by-ones
        
    }
    
    public void testLineCount() {
        System.out.println("testLineCount");
        
        OutWriter ow = new OutWriter ();
        
        String first = "This is the first string";
        String second = "This is the second string, ain't it?";
        String third = "This is the third string";
        String appended = "Appended string to last line";
        
        ow.println(first);
        
        ow.println (second);
        
        ow.println (third);
        
        ow.flush();
        processEQ();
        Thread.yield();
        
        assertTrue ("Linecount should be 4 after printing 3 lines, not " +
            ow.getLines().getLineCount(), ow.getLines().getLineCount() == 4);
        
        ow.print(appended);
        ow.print(appended);
        ow.flush();

        assertTrue("Linecount should be still 4 after appending text to last line, not " +
                ow.getLines().getLineCount(), ow.getLines().getLineCount() == 4);

    }
    
    public void testAddChangeListener() {
        System.out.println("testAddChangeListener");
        OutWriter ow = new OutWriter ();

        CL cl = new CL();
        try {
            ow.getLines().addChangeListener(cl);
        } catch (Exception e) {
            e.printStackTrace();
            fail ("Caught exception " + e);
        }

        String first = "This is the first string";
        String second = "This is the second string, ain't it?";
        String third = "This is the third string";
        
        ow.println(first);
        ow.println (second);
        ow.println (third);
        ow.flush();
        processEQ();
        cl.assertChanged();
    }

    public void testMultilineText() {
        System.out.println("testMultilineText");
        OutWriter ow = new OutWriter ();
        String threeLines = "This is\nthree lines of\nText";
        ow.print(threeLines);
        assertTrue ("Line count should be 3, not " + ow.getLines().getLineCount(), ow.getLines().getLineCount() == 3);
        ow.println("This is another line");
        assertTrue ("Line count should be 4, not " + ow.getLines().getLineCount(), ow.getLines().getLineCount() == 4);
        ow.println(threeLines);
        assertTrue ("Line count should be 7, not " + ow.getLines().getLineCount(), ow.getLines().getLineCount() == 7);
    }

    @RandomlyFails // NB-Core-Build #1887
    public void testRemoveChangeListener() {
        System.out.println("testRemoveChangeListener");
        
        
        
        OutWriter ow = new OutWriter ();
        
        CL cl = new CL();
        try {
            ow.getLines().addChangeListener(cl);
        } catch (Exception e) {
            e.printStackTrace();
            fail ("Caught exception " + e);
        }

        cl.clear();
        ow.getLines().removeChangeListener(cl);

        String first = "This is the first string";
        String second = "This is the second string, ain't it?";
        String third = "This is the third string";
        
        ow.println(first);
        
        ow.println (second);
        
        ow.println (third);
        
        ow.flush();        
        
        cl.assertNoChange();
    }
    
    public void testCheckDirty() {
        System.out.println("testCheckDirty");
        
        
        OutWriter ow = new OutWriter ();

        boolean dirty = ow.getLines().checkDirty(true);
        
        String first = "This is the a test";
        
        ow.println(first);
        
        
        //plan to delete checkDirty
    }
    
    public void testSubstring() {
        System.out.println("testSubstring");
        
        OutWriter ow = new OutWriter ();
        
        String first = "This is the first string";
        String second = "This is the second string, ain't it?";
        String third = "This is the third string";
        
        ow.println(first);
        ow.println (second);
        ow.println (third);
        ow.flush();
        
        //First test intra-line substrings
        
        String expected = first.substring(5, 15);
        String gotten = ow.getLines().getText (5, 15);
        System.err.println("\nGot " + gotten + "\n");
        
        assertEquals ("Should have gotten string \"" + expected + "\" but got \"" + gotten + "\"", expected, gotten);
        
        
    }    
    
    public void testPrintln() {
        System.out.println("testPrintln");

        try {
            OutWriter ow = new OutWriter ();

            String first = "This is a test string";

            ow.println(first);
            ow.flush();
            
            String firstExpected = first + OutWriter.LINE_SEPARATOR;
            String firstReceived = ow.getLines().getLine(0);
            
            assertEquals ("First line should be \"" + firstExpected + "\" but was \"" + firstReceived + "\"", firstExpected, firstReceived);
        
        } catch (Exception e) {
            e.printStackTrace();
            fail (e.getMessage());
        }
        
    }
    
    public void testReset() {
        System.out.println("testReset");
        
    }
    
    public void testFlush() {
        System.out.println("testFlush");
        
    }
    
    public void testClose() {
        System.out.println("testClose");
        
    }

    public void testCheckError() {
        System.out.println("testCheckError");
        
    }

    public void testSetError() {
        System.out.println("testSetError");
        
    }

    public void testWrite() {
        System.out.println("testWrite");
        try {
            OutWriter ow = new OutWriter ();
  
            ow.write('x');
            ow.write('y');
            ow.write('z');
            ow.println();
            ow.flush();
            assertEquals(2, ow.getLines().getLineCount());
            String firstReceived = ow.getLines().getLine(0);
            assertEquals ("xyz" + OutWriter.LINE_SEPARATOR, firstReceived);
        
            ow = new OutWriter();
            ow.println("firstline");
            ow.write('x');
            ow.println("yz");
            ow.flush();
            assertEquals(3, ow.getLines().getLineCount());
            firstReceived = ow.getLines().getLine(1);
            assertEquals ("xyz" + OutWriter.LINE_SEPARATOR, firstReceived);
            
            ow = new OutWriter();
            ow.println("firstline");
            ow.write(new char[] {'x', 'y', 'z'});
            ow.write(new char[] {'x', 'y', 'z'});
            ow.println("-end");
            ow.flush();
            assertEquals(3, ow.getLines().getLineCount());
            firstReceived = ow.getLines().getLine(1);
            assertEquals ("xyzxyz-end" + OutWriter.LINE_SEPARATOR, firstReceived);
            
            ow = new OutWriter();
            ow.write(new char[] {'x', 'y', '\n', 'z', 'z', 'z', '\n', 'A'});
            ow.flush();
            assertEquals(3, ow.getLines().getLineCount());
            assertEquals("xy" + OutWriter.LINE_SEPARATOR, ow.getLines().getLine(0));
            assertEquals("zzz" + OutWriter.LINE_SEPARATOR, ow.getLines().getLine(1));
            assertEquals("A", ow.getLines().getLine(2));
            
            ow = new OutWriter();
            ow.write(new char[] {'x', 'y', '\n', 'z', 'z', 'z', '\n'});
            ow.flush();
            assertEquals(3, ow.getLines().getLineCount());
            assertEquals("xy" + OutWriter.LINE_SEPARATOR, ow.getLines().getLine(0));
            assertEquals("zzz" + OutWriter.LINE_SEPARATOR, ow.getLines().getLine(1));
            
            ow = new OutWriter();
            ow.write(new char[] {'\n', '\n', '\n', 'z', 'z', 'z', '\n'});
            ow.flush();
            assertEquals(5, ow.getLines().getLineCount());
            assertEquals(OutWriter.LINE_SEPARATOR, ow.getLines().getLine(0));
            assertEquals(OutWriter.LINE_SEPARATOR, ow.getLines().getLine(1));
            assertEquals(OutWriter.LINE_SEPARATOR, ow.getLines().getLine(2));
            assertEquals("zzz" + OutWriter.LINE_SEPARATOR, ow.getLines().getLine(3));
            
            ow = new OutWriter();
            ow.write("Some \u001B[31;42mColor \u001B[32;41mText and "
                    + "unsupported \u001B[3Ksequence");
            ow.flush();
            assertEquals(1, ow.getLines().getLineCount());
            LineInfo.Segment[] segments = ow.getLines().getLineInfo(0)
                    .getLineSegments().toArray(new LineInfo.Segment[4]);
            assertNull(segments[0].getCustomBackground());
            assertNull(segments[0].getCustomColor());
            assertEquals("Some ", ow.getLines().getText(
                    0, segments[0].getEnd()));

            Color secondSegmentFg = segments[1].getCustomColor();
            Color secondSegmentBg = segments[1].getCustomBackground();
            assertNotNull(secondSegmentBg);
            assertNotNull(secondSegmentFg);
            assertEquals("Color ", ow.getLines().getText(
                    segments[0].getEnd(), segments[1].getEnd()));

            assertNotNull(segments[2].getCustomBackground());
            assertNotNull(segments[2].getCustomColor());
            assertNotSame(secondSegmentBg, segments[2].getCustomBackground());
            assertNotSame(secondSegmentFg, segments[2].getCustomColor());
            assertEquals("Text and unsupported sequence", ow.getLines().getText(
                    segments[1].getEnd(), segments[2].getEnd()));
            assertNull(segments[3]);
            
        } catch (Exception e) {
            e.printStackTrace();
            fail (e.getMessage());
        }
    }
    
    public void testClearLineANSI() throws IOException {
        OutWriter ow = new OutWriter();
        for (int i = 0; i <= 10; i++) {
            ow.print("\u001B[2K" + i + "0%: ");
            for (int j = 0; j < i; j++) {
                ow.print("=");
            }
        }
        ow.println();
        assertEquals("100%: ==========\n", ow.getLines().getLine(0));
    }

    public void testWriteWithBackspace() throws IOException {

        OutWriter ow = new OutWriter();

        ow.write("Helle\bo World");
        assertEquals("Hello World", ow.getLines().getLine(0));
    }

    public void testWriteLineTerminators() throws IOException {

        OutWriter ow = new OutWriter();

        ow.write("Months:\nJanuary\nFebruary\nMarch\r\nApril\n\r\nMay");

        assertEquals("January\n", ow.getLines().getLine(1));
        assertEquals("February\n", ow.getLines().getLine(2));
        assertEquals("March\n", ow.getLines().getLine(3));
        assertEquals("April\n", ow.getLines().getLine(4));
        assertEquals("\n", ow.getLines().getLine(5));
        assertEquals("May", ow.getLines().getLine(6));
    }

    public void testCarriageReturn() throws IOException {

        OutWriter ow = new OutWriter();

        ow.write("Test\rRewrite\n");
        ow.write("Original\rNew\n");

        ow.write("Test1");
        ow.write("\rReplace1\n");

        ow.write("Test2");
        ow.write("\rRep2\n");

        assertEquals("Rewrite\n", ow.getLines().getLine(0));
        assertEquals("New\n", ow.getLines().getLine(1));
        assertEquals("Replace1\n", ow.getLines().getLine(2));
        assertEquals("Rep2\n", ow.getLines().getLine(3));
    }

    public void testBackspaceChars() throws IOException {

        OutWriter ow = new OutWriter();

        ow.write("Enter input:\n");
        ow.write(">");
        ow.write("\b");
        ow.write("Output\n");

        assertEquals("Enter input:\n", ow.getLines().getLine(0));
        assertEquals("Output\n", ow.getLines().getLine(1));
    }

    public void testWritePartial() {
        System.out.println("testWritePartial");
        try {
            OutWriter ow = new OutWriter ();

            ow.write('x');
            assertEquals(1, ow.getLines().getLineCount());
            assertEquals ("x", ow.getLines().getLine(0));
            ow.write('y');
            assertEquals ("xy", ow.getLines().getLine(0));
            ow.write('z');
            assertEquals ("xyz", ow.getLines().getLine(0));
            ow.println();
            assertEquals ("xyz" + OutWriter.LINE_SEPARATOR, ow.getLines().getLine(0));
            ow.write('a');
            assertEquals(2, ow.getLines().getLineCount());
            assertEquals ("a", ow.getLines().getLine(1));
            
            
            ow = new OutWriter();
            ow.write(new char[] { 'x', 'y', 'z', '\n', 'A'});
            assertEquals(2, ow.getLines().getLineCount());
            assertEquals ("xyz" + OutWriter.LINE_SEPARATOR, ow.getLines().getLine(0));
            assertEquals ("A", ow.getLines().getLine(1));
            ow.write('B');
            assertEquals(2, ow.getLines().getLineCount());
            assertEquals ("AB", ow.getLines().getLine(1));
            ow.println("CD");
            assertEquals(3, ow.getLines().getLineCount());
            assertEquals ("ABCD" + OutWriter.LINE_SEPARATOR, ow.getLines().getLine(1));
            
        } catch (Exception e) {
            e.printStackTrace();
            fail (e.getMessage());
        }
        
    }
    
    public void testLimitReached() throws IOException {
        OutWriter ow = new OutWriter();
        ((AbstractLines) ow.getLines()).setOutputLimits(
                new OutputLimits(6, 1024, 3));

        ow.println("first");
        ow.println("second");
        ow.println("third");
        ow.println("fourth");
        assertEquals(5, ow.getLines().getLineCount());

        ow.println("fifth"); // sixth line, remove three lines
        assertEquals(3, ow.getLines().getLineCount());
        assertEquals("fourth\n", ow.getLines().getLine(0));
        assertEquals("fifth\n", ow.getLines().getLine(1));
        assertEquals("", ow.getLines().getLine(2));
    }

    /**
     * Test for bug 245125 - NetBeans UI stops responding, ended with black
     * window.
     *
     * @throws IOException
     * @throws InterruptedException
     */
    public void testSynchronizationOfGetLineInfo()
            throws IOException, InterruptedException {

        final OutWriter ow = new OutWriter();
        ((AbstractLines) ow.getLines()).setOutputLimits(
                new OutputLimits(4, 1024, 2));

        Thread writingThread = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    delay(1);
                    ow.println("test" + i, new OutputListener() {

                        @Override
                        public void outputLineSelected(OutputEvent ev) {
                        }

                        @Override
                        public void outputLineAction(OutputEvent ev) {
                        }

                        @Override
                        public void outputLineCleared(OutputEvent ev) {
                        }
                    });
                }
            }
        }, "writingThread");
        writingThread.start();
        for (int i = 0; i < 1000; i++) {
            LineInfo lineInfo = ow.getLines().getLineInfo(
                    ow.getLines().getLineCount() - 1);
            assertNotNull(lineInfo);
        }
        writingThread.join();
    }

    /**
     * Test for bug 232547 - There are LineInfo object even for lines that has
     * no special properties.
     */
    public void testNoExtraLineInfoObjectsByPrintln() {

        OutWriter ow = new OutWriter();
        ow.println("Months:\nJanuary\nFebruary\rMarch\r\nApril\n\rMay");
        assertNoLineInfo(ow);
    }

    /**
     * Test for bug 232547 - There are LineInfo object even for lines that has
     * no special properties.
     */
    public void testNoExtraLineInfoObjectsByPrint() {

        OutWriter ow = new OutWriter();
        ow.print("Months:\nJanuary\nFebruary\rMarch\r\nApril\n\rMay",
                null, false);
        assertNoLineInfo(ow);
    }

    /**
     * Test for bug 244645 - Npm install tab looks to be still active although
     * it's done.
     */
    public void testNotClosedAfterCreation() {
        OutWriter ow = new OutWriter();
        assertFalse(ow.isClosed());
        ow.dispose();
        assertTrue(ow.isClosed());
    }

    private void delay(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException ex) {
            Exceptions.printStackTrace(ex);
        }
    }

    /**
     * Assert that no line in the OutWriter has assigned a LineInfo object. That
     * would be waste of memory if the lines have no special properties.
     */
    private void assertNoLineInfo(OutWriter ow) {
        AbstractLines lines = (AbstractLines) ow.getLines();
        for (int i = 0; i < lines.getLineCount(); i++) {
            assertNull("No LineInfo for basic line",
                    lines.getExistingLineInfo(i));
        }
    }

    void processEQ() {
        try {
            SwingUtilities.invokeAndWait(new Runnable() {

                public void run() {
                    System.currentTimeMillis();
                }
            });
        } catch (InterruptedException ex) {
            Exceptions.printStackTrace(ex);
        } catch (InvocationTargetException ex) {
            Exceptions.printStackTrace(ex);
        }
    }

    private class CL implements ChangeListener {
        
        public void assertChanged () {
            ChangeEvent oldCE = ce;
            ce = null;
            assertTrue ("No change happened", oldCE != null);
        }
        
        public void assertNoChange() {
            ChangeEvent oldCE = ce;
            ce = null;
            assertFalse ("Change happened", oldCE != null);
        }
        
        private ChangeEvent ce = null;
        public void stateChanged(ChangeEvent changeEvent) {
            ce = changeEvent;
        }
        
        void clear() {
            ce = null;
        }
    }
}
