# -*- test-case-name: twisted.conch.test.test_helper -*-
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
# See LICENSE for details.

from twisted.conch.insults import helper
from twisted.conch.insults.insults import ServerProtocol, ClientProtocol
from twisted.conch.insults.insults import G0, G1, G2, G3
from twisted.conch.insults.insults import modes
from twisted.conch.insults.insults import NORMAL, BOLD, UNDERLINE, BLINK, REVERSE_VIDEO

from twisted.trial import unittest

WIDTH = 80
HEIGHT = 24

class BufferTestCase(unittest.TestCase):
    def setUp(self):
        self.term = helper.TerminalBuffer()
        self.term.connectionMade()

    def testInitialState(self):
        self.assertEquals(self.term.width, WIDTH)
        self.assertEquals(self.term.height, HEIGHT)
        self.assertEquals(str(self.term),
                          '\n' * (HEIGHT - 1))
        self.assertEquals(self.term.reportCursorPosition(), (0, 0))

    def testCursorDown(self):
        self.term.cursorDown(3)
        self.assertEquals(self.term.reportCursorPosition(), (0, 3))
        self.term.cursorDown()
        self.assertEquals(self.term.reportCursorPosition(), (0, 4))
        self.term.cursorDown(HEIGHT)
        self.assertEquals(self.term.reportCursorPosition(), (0, HEIGHT - 1))

    def testCursorUp(self):
        self.term.cursorUp(5)
        self.assertEquals(self.term.reportCursorPosition(), (0, 0))

        self.term.cursorDown(20)
        self.term.cursorUp(1)
        self.assertEquals(self.term.reportCursorPosition(), (0, 19))

        self.term.cursorUp(19)
        self.assertEquals(self.term.reportCursorPosition(), (0, 0))

    def testCursorForward(self):
        self.term.cursorForward(2)
        self.assertEquals(self.term.reportCursorPosition(), (2, 0))
        self.term.cursorForward(2)
        self.assertEquals(self.term.reportCursorPosition(), (4, 0))
        self.term.cursorForward(WIDTH)
        self.assertEquals(self.term.reportCursorPosition(), (WIDTH, 0))

    def testCursorBackward(self):
        self.term.cursorForward(10)
        self.term.cursorBackward(2)
        self.assertEquals(self.term.reportCursorPosition(), (8, 0))
        self.term.cursorBackward(7)
        self.assertEquals(self.term.reportCursorPosition(), (1, 0))
        self.term.cursorBackward(1)
        self.assertEquals(self.term.reportCursorPosition(), (0, 0))
        self.term.cursorBackward(1)
        self.assertEquals(self.term.reportCursorPosition(), (0, 0))

    def testCursorPositioning(self):
        self.term.cursorPosition(3, 9)
        self.assertEquals(self.term.reportCursorPosition(), (3, 9))

    def testSimpleWriting(self):
        s = "Hello, world."
        self.term.write(s)
        self.assertEquals(
            str(self.term),
            s + '\n' +
            '\n' * (HEIGHT - 2))

    def testOvertype(self):
        s = "hello, world."
        self.term.write(s)
        self.term.cursorBackward(len(s))
        self.term.resetModes([modes.IRM])
        self.term.write("H")
        self.assertEquals(
            str(self.term),
            ("H" + s[1:]) + '\n' +
            '\n' * (HEIGHT - 2))

    def testInsert(self):
        s = "ello, world."
        self.term.write(s)
        self.term.cursorBackward(len(s))
        self.term.setModes([modes.IRM])
        self.term.write("H")
        self.assertEquals(
            str(self.term),
            ("H" + s) + '\n' +
            '\n' * (HEIGHT - 2))

    def testWritingInTheMiddle(self):
        s = "Hello, world."
        self.term.cursorDown(5)
        self.term.cursorForward(5)
        self.term.write(s)
        self.assertEquals(
            str(self.term),
            '\n' * 5 +
            (self.term.fill * 5) + s + '\n' +
            '\n' * (HEIGHT - 7))

    def testWritingWrappedAtEndOfLine(self):
        s = "Hello, world."
        self.term.cursorForward(WIDTH - 5)
        self.term.write(s)
        self.assertEquals(
            str(self.term),
            s[:5].rjust(WIDTH) + '\n' +
            s[5:] + '\n' +
            '\n' * (HEIGHT - 3))

    def testIndex(self):
        self.term.index()
        self.assertEquals(self.term.reportCursorPosition(), (0, 1))
        self.term.cursorDown(HEIGHT)
        self.assertEquals(self.term.reportCursorPosition(), (0, HEIGHT - 1))
        self.term.index()
        self.assertEquals(self.term.reportCursorPosition(), (0, HEIGHT - 1))

    def testReverseIndex(self):
        self.term.reverseIndex()
        self.assertEquals(self.term.reportCursorPosition(), (0, 0))
        self.term.cursorDown(2)
        self.assertEquals(self.term.reportCursorPosition(), (0, 2))
        self.term.reverseIndex()
        self.assertEquals(self.term.reportCursorPosition(), (0, 1))

    def testNextLine(self):
        self.term.nextLine()
        self.assertEquals(self.term.reportCursorPosition(), (0, 1))
        self.term.cursorForward(5)
        self.assertEquals(self.term.reportCursorPosition(), (5, 1))
        self.term.nextLine()
        self.assertEquals(self.term.reportCursorPosition(), (0, 2))

    def testSaveCursor(self):
        self.term.cursorDown(5)
        self.term.cursorForward(7)
        self.assertEquals(self.term.reportCursorPosition(), (7, 5))
        self.term.saveCursor()
        self.term.cursorDown(7)
        self.term.cursorBackward(3)
        self.assertEquals(self.term.reportCursorPosition(), (4, 12))
        self.term.restoreCursor()
        self.assertEquals(self.term.reportCursorPosition(), (7, 5))

    def testSingleShifts(self):
        self.term.singleShift2()
        self.term.write('Hi')

        ch = self.term.getCharacter(0, 0)
        self.assertEquals(ch[0], 'H')
        self.assertEquals(ch[1].charset, G2)

        ch = self.term.getCharacter(1, 0)
        self.assertEquals(ch[0], 'i')
        self.assertEquals(ch[1].charset, G0)

        self.term.singleShift3()
        self.term.write('!!')

        ch = self.term.getCharacter(2, 0)
        self.assertEquals(ch[0], '!')
        self.assertEquals(ch[1].charset, G3)

        ch = self.term.getCharacter(3, 0)
        self.assertEquals(ch[0], '!')
        self.assertEquals(ch[1].charset, G0)

    def testShifting(self):
        s1 = "Hello"
        s2 = "World"
        s3 = "Bye!"
        self.term.write("Hello\n")
        self.term.shiftOut()
        self.term.write("World\n")
        self.term.shiftIn()
        self.term.write("Bye!\n")

        g = G0
        h = 0
        for s in (s1, s2, s3):
            for i in range(len(s)):
                ch = self.term.getCharacter(i, h)
                self.assertEquals(ch[0], s[i])
                self.assertEquals(ch[1].charset, g)
            g = g == G0 and G1 or G0
            h += 1

    def testGraphicRendition(self):
        self.term.selectGraphicRendition(BOLD, UNDERLINE, BLINK, REVERSE_VIDEO)
        self.term.write('W')
        self.term.selectGraphicRendition(NORMAL)
        self.term.write('X')
        self.term.selectGraphicRendition(BLINK)
        self.term.write('Y')
        self.term.selectGraphicRendition(BOLD)
        self.term.write('Z')

        ch = self.term.getCharacter(0, 0)
        self.assertEquals(ch[0], 'W')
        self.failUnless(ch[1].bold)
        self.failUnless(ch[1].underline)
        self.failUnless(ch[1].blink)
        self.failUnless(ch[1].reverseVideo)

        ch = self.term.getCharacter(1, 0)
        self.assertEquals(ch[0], 'X')
        self.failIf(ch[1].bold)
        self.failIf(ch[1].underline)
        self.failIf(ch[1].blink)
        self.failIf(ch[1].reverseVideo)

        ch = self.term.getCharacter(2, 0)
        self.assertEquals(ch[0], 'Y')
        self.failUnless(ch[1].blink)
        self.failIf(ch[1].bold)
        self.failIf(ch[1].underline)
        self.failIf(ch[1].reverseVideo)

        ch = self.term.getCharacter(3, 0)
        self.assertEquals(ch[0], 'Z')
        self.failUnless(ch[1].blink)
        self.failUnless(ch[1].bold)
        self.failIf(ch[1].underline)
        self.failIf(ch[1].reverseVideo)

    def testColorAttributes(self):
        s1 = "Merry xmas"
        s2 = "Just kidding"
        self.term.selectGraphicRendition(helper.FOREGROUND + helper.RED,
                                         helper.BACKGROUND + helper.GREEN)
        self.term.write(s1 + "\n")
        self.term.selectGraphicRendition(NORMAL)
        self.term.write(s2 + "\n")

        for i in range(len(s1)):
            ch = self.term.getCharacter(i, 0)
            self.assertEquals(ch[0], s1[i])
            self.assertEquals(ch[1].charset, G0)
            self.assertEquals(ch[1].bold, False)
            self.assertEquals(ch[1].underline, False)
            self.assertEquals(ch[1].blink, False)
            self.assertEquals(ch[1].reverseVideo, False)
            self.assertEquals(ch[1].foreground, helper.RED)
            self.assertEquals(ch[1].background, helper.GREEN)

        for i in range(len(s2)):
            ch = self.term.getCharacter(i, 1)
            self.assertEquals(ch[0], s2[i])
            self.assertEquals(ch[1].charset, G0)
            self.assertEquals(ch[1].bold, False)
            self.assertEquals(ch[1].underline, False)
            self.assertEquals(ch[1].blink, False)
            self.assertEquals(ch[1].reverseVideo, False)
            self.assertEquals(ch[1].foreground, helper.WHITE)
            self.assertEquals(ch[1].background, helper.BLACK)

    def testEraseLine(self):
        s1 = 'line 1'
        s2 = 'line 2'
        s3 = 'line 3'
        self.term.write('\n'.join((s1, s2, s3)) + '\n')
        self.term.cursorPosition(1, 1)
        self.term.eraseLine()

        self.assertEquals(
            str(self.term),
            s1 + '\n' +
            '\n' +
            s3 + '\n' +
            '\n' * (HEIGHT - 4))

    def testEraseToLineEnd(self):
        s = 'Hello, world.'
        self.term.write(s)
        self.term.cursorBackward(5)
        self.term.eraseToLineEnd()
        self.assertEquals(
            str(self.term),
            s[:-5] + '\n' +
            '\n' * (HEIGHT - 2))

    def testEraseToLineBeginning(self):
        s = 'Hello, world.'
        self.term.write(s)
        self.term.cursorBackward(5)
        self.term.eraseToLineBeginning()
        self.assertEquals(
            str(self.term),
            s[-4:].rjust(len(s)) + '\n' +
            '\n' * (HEIGHT - 2))

    def testEraseDisplay(self):
        self.term.write('Hello world\n')
        self.term.write('Goodbye world\n')
        self.term.eraseDisplay()

        self.assertEquals(
            str(self.term),
            '\n' * (HEIGHT - 1))

    def testEraseToDisplayEnd(self):
        s1 = "Hello world"
        s2 = "Goodbye world"
        self.term.write('\n'.join((s1, s2, '')))
        self.term.cursorPosition(5, 1)
        self.term.eraseToDisplayEnd()

        self.assertEquals(
            str(self.term),
            s1 + '\n' +
            s2[:5] + '\n' +
            '\n' * (HEIGHT - 3))

    def testEraseToDisplayBeginning(self):
        s1 = "Hello world"
        s2 = "Goodbye world"
        self.term.write('\n'.join((s1, s2)))
        self.term.cursorPosition(5, 1)
        self.term.eraseToDisplayBeginning()

        self.assertEquals(
            str(self.term),
            '\n' +
            s2[6:].rjust(len(s2)) + '\n' +
            '\n' * (HEIGHT - 3))

    def testLineInsertion(self):
        s1 = "Hello world"
        s2 = "Goodbye world"
        self.term.write('\n'.join((s1, s2)))
        self.term.cursorPosition(7, 1)
        self.term.insertLine()

        self.assertEquals(
            str(self.term),
            s1 + '\n' +
            '\n' +
            s2 + '\n' +
            '\n' * (HEIGHT - 4))

    def testLineDeletion(self):
        s1 = "Hello world"
        s2 = "Middle words"
        s3 = "Goodbye world"
        self.term.write('\n'.join((s1, s2, s3)))
        self.term.cursorPosition(9, 1)
        self.term.deleteLine()

        self.assertEquals(
            str(self.term),
            s1 + '\n' +
            s3 + '\n' +
            '\n' * (HEIGHT - 3))

class FakeDelayedCall:
    called = False
    cancelled = False
    def __init__(self, fs, timeout, f, a, kw):
        self.fs = fs
        self.timeout = timeout
        self.f = f
        self.a = a
        self.kw = kw

    def active(self):
        return not (self.cancelled or self.called)

    def cancel(self):
        self.cancelled = True
#        self.fs.calls.remove(self)

    def call(self):
        self.called = True
        self.f(*self.a, **self.kw)

class FakeScheduler:
    def __init__(self):
        self.calls = []

    def callLater(self, timeout, f, *a, **kw):
        self.calls.append(FakeDelayedCall(self, timeout, f, a, kw))
        return self.calls[-1]

class ExpectTestCase(unittest.TestCase):
    def setUp(self):
        self.term = helper.ExpectableBuffer()
        self.term.connectionMade()
        self.fs = FakeScheduler()

    def testSimpleString(self):
        result = []
        d = self.term.expect("hello world", timeout=1, scheduler=self.fs)
        d.addCallback(result.append)

        self.term.write("greeting puny earthlings\n")
        self.failIf(result)
        self.term.write("hello world\n")
        self.failUnless(result)
        self.assertEquals(result[0].group(), "hello world")
        self.assertEquals(len(self.fs.calls), 1)
        self.failIf(self.fs.calls[0].active())

    def testBrokenUpString(self):
        result = []
        d = self.term.expect("hello world")
        d.addCallback(result.append)

        self.failIf(result)
        self.term.write("hello ")
        self.failIf(result)
        self.term.write("worl")
        self.failIf(result)
        self.term.write("d")
        self.failUnless(result)
        self.assertEquals(result[0].group(), "hello world")


    def testMultiple(self):
        result = []
        d1 = self.term.expect("hello ")
        d1.addCallback(result.append)
        d2 = self.term.expect("world")
        d2.addCallback(result.append)

        self.failIf(result)
        self.term.write("hello")
        self.failIf(result)
        self.term.write(" ")
        self.assertEquals(len(result), 1)
        self.term.write("world")
        self.assertEquals(len(result), 2)
        self.assertEquals(result[0].group(), "hello ")
        self.assertEquals(result[1].group(), "world")

    def testSynchronous(self):
        self.term.write("hello world")

        result = []
        d = self.term.expect("hello world")
        d.addCallback(result.append)
        self.failUnless(result)
        self.assertEquals(result[0].group(), "hello world")

    def testMultipleSynchronous(self):
        self.term.write("goodbye world")

        result = []
        d1 = self.term.expect("bye")
        d1.addCallback(result.append)
        d2 = self.term.expect("world")
        d2.addCallback(result.append)

        self.assertEquals(len(result), 2)
        self.assertEquals(result[0].group(), "bye")
        self.assertEquals(result[1].group(), "world")

    def _cbTestTimeoutFailure(self, res):
        self.assert_(hasattr(res, 'type'))
        self.assertEqual(res.type, helper.ExpectationTimeout)

    def testTimeoutFailure(self):
        d = self.term.expect("hello world", timeout=1, scheduler=self.fs)
        d.addBoth(self._cbTestTimeoutFailure)
        self.fs.calls[0].call()

    def testOverlappingTimeout(self):
        self.term.write("not zoomtastic")

        result = []
        d1 = self.term.expect("hello world", timeout=1, scheduler=self.fs)
        d1.addBoth(self._cbTestTimeoutFailure)
        d2 = self.term.expect("zoom")
        d2.addCallback(result.append)

        self.fs.calls[0].call()

        self.assertEquals(len(result), 1)
        self.assertEquals(result[0].group(), "zoom")

