# Copyright 2013  Lars Wirzenius
#
# This program 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.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#
# =*= License: GPL-3+ =*=


import cliapp

import yarnlib


class BlockError(cliapp.AppException):

    pass


# Parse a sequence of textual blocks into scenario and Implementation
# objects, and their constituent objects.

class BlockParser(object):

    def __init__(self):
        self.scenarios = []
        self.implementations = []
        self.line_parsers = {
            'SCENARIO': self.parse_scenario,
            'ASSUMING': self.parse_assuming,
            'GIVEN': self.parse_given,
            'WHEN': self.parse_when,
            'THEN': self.parse_then,
            'FINALLY': self.parse_finally,
            'AND': self.parse_and,
            '...': self.parse_continuation,
            'IMPLEMENTS': self.parse_implementing,
            'EXAMPLE': self.parse_example,
        }

    def parse_blocks(self, blocks):
        while blocks:
            blocks = self.parse_one(blocks)

    def parse_one(self, blocks):
        assert blocks
        block = blocks[0]
        assert block
        t = block.split('\n', 1)
        assert len(t) in [1,2]
        if len(t) == 1:
            line1 = block
            block = ''
        else:
            line1, block = t
        if block:
            blocks[0] = block
        else:
            del blocks[0]

        words = line1.split()
        if not words:
            return blocks
        rest = ' '.join(words[1:])

        for keyword in self.line_parsers:
            if words[0] == keyword:
                return self.line_parsers[keyword](rest, blocks)

        raise BlockError("Syntax error: unknown step: %s" % line1)

    def parse_scenario(self, line, blocks):
        self.scenarios.append(yarnlib.Scenario(line))
        return blocks

    def parse_simple(self, what, line, blocks):
        if not self.scenarios:
            raise BlockError('Syntax errror: %s before SCENARIO' % what)
        step = yarnlib.ScenarioStep(what, line)
        self.scenarios[-1].steps.append(step)
        return blocks

    def parse_assuming(self, line, blocks):
        return self.parse_simple('ASSUMING', line, blocks)

    def parse_given(self, line, blocks):
        return self.parse_simple('GIVEN', line, blocks)

    def parse_when(self, line, blocks):
        return self.parse_simple('WHEN', line, blocks)

    def parse_then(self, line, blocks):
        return self.parse_simple('THEN', line, blocks)

    def parse_finally(self, line, blocks):
        return self.parse_simple('FINALLY', line, blocks)

    def parse_and(self, line, blocks):
        if not self.scenarios:
            raise BlockError('Syntax errror: AND before SCENARIO')
        scenario = self.scenarios[-1]
        if not scenario.steps:
            raise BlockError(
                'Syntax errror: AND before what it would continue')
        step = scenario.steps[-1]
        assert step.what in self.line_parsers
        return self.line_parsers[step.what](line, blocks)

    def parse_continuation(self, line, blocks):
        scenario = self.scenarios[-1]
        step = scenario.steps[-1]
        text = '%s %s' % (step.text, line)
        del scenario.steps[-1]
        return self.line_parsers[step.what](text, blocks)

    def parse_implementing(self, line, blocks):
        words = line.split()
        if len(words) < 2:
            raise BlockError(
                'Syntax error: IMPLEMENTS must have what and regexp')
        what = words[0]
        regexp = ' '.join(words[1:])
        if blocks:
            block = blocks[0]
            shell = []
            rest = []
            for block_line in block.splitlines():
                if rest or block_line.startswith('IMPLEMENTS'):
                    rest.append(block_line)
                else:
                    shell.append(block_line)
            shell = '\n'.join(shell)
            if rest:
                blocks[0] = '\n'.join(rest)
            else:
                del blocks[0]
        else:
            shell = ''
        implementation = yarnlib.Implementation(what, regexp, shell)
        self.implementations.append(implementation)
        return blocks

    def parse_example(self, line, blocks):
        if blocks:
            del blocks[0]
        return blocks
