#! /usr/bin/env python3

# $Id: test_statemachine.py 9795 2024-07-31 08:35:47Z aa-turner $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.

"""
Test module for statemachine.py.
"""

from pathlib import Path
import re
import sys
import unittest

if __name__ == '__main__':
    # prepend the "docutils root" to the Python library path
    # so we import the local `docutils` package.
    sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from docutils import statemachine


debug = False
testtext = statemachine.string2lines("""\
First paragraph.

- This is a bullet list. First list item.
  Second line of first para.

  Second para.

      block quote

- Second list item. Example::

        a
      literal
           block

Last paragraph.""")
expected = ('StateMachine1 text1 blank1 bullet1 known_indent1 '
            'StateMachine2 text2 text2 blank2 text2 blank2 indent2 '
            'StateMachine3 text3 blank3 finished3 finished2 '
            'bullet1 known_indent1 '
            'StateMachine2 text2 blank2 literalblock2(4) finished2 '
            'text1 finished1').split()
para1 = testtext[:2]
item1 = [line[2:] for line in testtext[2:9]]
item2 = [line[2:] for line in testtext[9:-1]]
lbindent = 6
literalblock = [line[lbindent:] for line in testtext[11:-1]]
para2 = testtext[-1]


class MockState(statemachine.StateWS):

    patterns = {'bullet': re.compile(r'- '),
                'text': ''}
    initial_transitions = ['bullet', ['text']]
    levelholder = [0]

    def bof(self, context):
        self.levelholder[0] += 1
        self.level = self.levelholder[0]
        if self.debug:
            print('StateMachine%s' % self.level, file=sys.stderr)
        return [], ['StateMachine%s' % self.level]

    def blank(self, match, context, next_state):
        result = ['blank%s' % self.level]
        if self.debug:
            print('blank%s' % self.level, file=sys.stderr)
        if context and context[-1] and context[-1][-2:] == '::':
            result.extend(self.literalblock())
        return [], None, result

    def indent(self, match, context, next_state):
        if self.debug:
            print('indent%s' % self.level, file=sys.stderr)
        context, next_state, result = statemachine.StateWS.indent(
              self, match, context, next_state)
        return context, next_state, ['indent%s' % self.level] + result

    def known_indent(self, match, context, next_state):
        if self.debug:
            print('known_indent%s' % self.level, file=sys.stderr)
        context, next_state, result = statemachine.StateWS.known_indent(
              self, match, context, next_state)
        return context, next_state, ['known_indent%s' % self.level] + result

    def bullet(self, match, context, next_state):
        if self.debug:
            print('bullet%s' % self.level, file=sys.stderr)
        context, next_state, result \
            = self.known_indent(match, context, next_state)
        return [], next_state, ['bullet%s' % self.level] + result

    def text(self, match, context, next_state):
        if self.debug:
            print('text%s' % self.level, file=sys.stderr)
        return [match.string], next_state, ['text%s' % self.level]

    def literalblock(self):
        _indented, indent, _offset, _good = self.state_machine.get_indented()
        if self.debug:
            print('literalblock%s(%s)' % (self.level, indent), file=sys.stderr)
        return ['literalblock%s(%s)' % (self.level, indent)]

    def eof(self, context):
        self.levelholder[0] -= 1
        if self.debug:
            print('finished%s' % self.level, file=sys.stderr)
        return ['finished%s' % self.level]


class EmptySMTests(unittest.TestCase):

    def setUp(self):
        self.sm = statemachine.StateMachine(
              state_classes=[], initial_state='State')
        self.sm.debug = debug

    def test_add_state(self):
        self.sm.add_state(statemachine.State)
        self.assertTrue(len(self.sm.states) == 1)
        with self.assertRaises(statemachine.DuplicateStateError):
            self.sm.add_state(statemachine.State)
        self.sm.add_state(statemachine.StateWS)
        self.assertTrue(len(self.sm.states) == 2)

    def test_add_states(self):
        self.sm.add_states((statemachine.State, statemachine.StateWS))
        self.assertEqual(len(self.sm.states), 2)

    def test_get_state(self):
        self.assertRaises(statemachine.UnknownStateError, self.sm.get_state)
        self.sm.add_states((statemachine.State, statemachine.StateWS))
        with self.assertRaises(statemachine.UnknownStateError):
            self.sm.get_state('unknownState')
        self.assertTrue(isinstance(self.sm.get_state('State'),
                                   statemachine.State))
        self.assertTrue(isinstance(self.sm.get_state('StateWS'),
                                   statemachine.State))
        self.assertEqual(self.sm.current_state, 'StateWS')


class EmptySMWSTests(EmptySMTests):

    def setUp(self):
        self.sm = statemachine.StateMachineWS(
              state_classes=[], initial_state='State')
        self.sm.debug = debug


class SMWSTests(unittest.TestCase):

    def setUp(self):
        self.sm = statemachine.StateMachineWS([MockState], 'MockState',
                                              debug=debug)
        self.sm.debug = debug
        self.sm.states['MockState'].levelholder[0] = 0

    def tearDown(self):
        self.sm.unlink()

    def test___init__(self):
        self.assertEqual(list(self.sm.states.keys()), ['MockState'])
        self.assertEqual(len(self.sm.states['MockState'].transitions), 4)

    def test_get_indented(self):
        self.sm.input_lines = statemachine.StringList(testtext)
        self.sm.line_offset = -1
        self.sm.next_line(3)
        indented, offset, good = self.sm.get_known_indented(2)
        self.assertEqual(indented, item1)
        self.assertEqual(offset, len(para1))
        self.assertTrue(good)
        self.sm.next_line()
        indented, offset, good = self.sm.get_known_indented(2)
        self.assertEqual(indented, item2)
        self.assertEqual(offset, len(para1) + len(item1))
        self.assertTrue(good)
        self.sm.previous_line(3)
        if self.sm.debug:
            print('\ntest_get_indented: self.sm.line:\n', self.sm.line)
        indented, indent, offset, good = self.sm.get_indented()
        if self.sm.debug:
            print('\ntest_get_indented: indented:\n', indented)
        self.assertEqual(indent, lbindent)
        self.assertEqual(indented, literalblock)
        self.assertEqual(offset, (len(para1) + len(item1) + len(item2)
                                  - len(literalblock)))
        self.assertTrue(good)

    def test_get_text_block(self):
        self.sm.input_lines = statemachine.StringList(testtext)
        self.sm.line_offset = -1
        self.sm.next_line()
        textblock = self.sm.get_text_block()
        self.assertEqual(textblock, testtext[:1])
        self.sm.next_line(2)
        textblock = self.sm.get_text_block()
        self.assertEqual(textblock, testtext[2:4])

    def test_get_text_block_flush_left(self):
        self.sm.input_lines = statemachine.StringList(testtext)
        self.sm.line_offset = -1
        self.sm.next_line()
        textblock = self.sm.get_text_block(flush_left=1)
        self.assertEqual(textblock, testtext[:1])
        self.sm.next_line(2)
        with self.assertRaises(statemachine.UnexpectedIndentationError):
            self.sm.get_text_block(flush_left=1)

    def test_run(self):
        self.assertEqual(self.sm.run(testtext), expected)


class EmptyClass:
    pass


class EmptyStateTests(unittest.TestCase):

    def setUp(self):
        self.state = statemachine.State(EmptyClass(), debug=debug)
        self.state.patterns = {'nop': 'dummy',
                               'nop2': 'dummy',
                               'nop3': 'dummy',
                               'bogus': 'dummy'}
        self.state.nop2 = self.state.nop3 = self.state.nop

    def test_add_transitions(self):
        self.assertEqual(len(self.state.transitions), 0)
        self.state.add_transitions(['None'], {'None': None})
        self.assertEqual(len(self.state.transitions), 1)
        with self.assertRaises(statemachine.UnknownTransitionError):
            self.state.add_transitions(['bogus'], {})
        with self.assertRaises(statemachine.DuplicateTransitionError):
            self.state.add_transitions(['None'], {'None': None})

    def test_add_transition(self):
        self.assertEqual(len(self.state.transitions), 0)
        self.state.add_transition('None', None)
        self.assertEqual(len(self.state.transitions), 1)
        with self.assertRaises(statemachine.DuplicateTransitionError):
            self.state.add_transition('None', None)

    def test_remove_transition(self):
        self.assertEqual(len(self.state.transitions), 0)
        self.state.add_transition('None', None)
        self.assertEqual(len(self.state.transitions), 1)
        self.state.remove_transition('None')
        self.assertEqual(len(self.state.transitions), 0)
        with self.assertRaises(statemachine.UnknownTransitionError):
            self.state.remove_transition('None')

    def test_make_transition(self):
        dummy = re.compile('dummy')
        self.assertEqual(self.state.make_transition('nop', 'bogus'),
                         (dummy, self.state.nop, 'bogus'))
        self.assertEqual(self.state.make_transition('nop'),
                         (dummy, self.state.nop,
                          self.state.__class__.__name__))
        with self.assertRaises(statemachine.TransitionPatternNotFound):
            self.state.make_transition('None')
        with self.assertRaises(statemachine.TransitionMethodNotFound):
            self.state.make_transition('bogus')

    def test_make_transitions(self):
        dummy = re.compile('dummy')
        self.assertEqual(self.state.make_transitions(('nop', ['nop2'],
                                                      ('nop3', 'bogus'))),
                         (['nop', 'nop2', 'nop3'],
                          {'nop': (dummy, self.state.nop,
                                   self.state.__class__.__name__),
                           'nop2': (dummy, self.state.nop2,
                                    self.state.__class__.__name__),
                           'nop3': (dummy, self.state.nop3, 'bogus')}))


class MiscTests(unittest.TestCase):

    s2l_string = "hello\tthere\thow are\tyou?\n\tI'm fine\tthanks.\n"
    s2l_expected = ['hello   there   how are you?',
                    "        I'm fine        thanks."]

    def test_string2lines(self):
        self.assertEqual(statemachine.string2lines(self.s2l_string),
                         self.s2l_expected)


if __name__ == '__main__':
    unittest.main()
