# Copyright (C) 2017 The Meme Factory, Inc.  http://www.meme.com/
#
# Licensed 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.

# Karl O. Pinc <kop@meme.com>

import pytest

from enforcer import exceptions as ex
from enforcer import fstree
from enforcer import get_rules
from enforcer import parse_rules
from enforcer import startup

import libtest


#
# Helper functions
#

def sample_config():
    '''Helper function: return a config (dict) with a rules_path'''
    return {'rules_source': get_rules.FILE_SOURCE,
            'rules_path': 'path',
            'begin_marker': 'begin',
            'max_depth': 4}


def sample_rules():
    '''Helper function: Produce a basic rule file.'''
    return ['remark1\n',
            'remark2\n',
            'begin\n',
            'one\n',
            ',two\n',
            ',,three\n',
            'four\n']


def sample_tokens(rules):
    '''Helper function: Tokenize the basic rule file.'''
    return parse_rules.lex_rules_fs_struct(sample_config(), rules)


def clear_error_state():
    '''Clear out the global error state'''
    try:
        startup.em.exit_if_errored()
    except SystemExit:
        pass

#
# Function tests
#


# get_file_id()

def test_get_file_id_file():
    '''Does a file source produce expected results?'''
    path = 'some path'
    assert (parse_rules.get_file_id({'rules_source': get_rules.FILE_SOURCE,
                                     'rules_path': path}) ==
            path)


def test_get_file_id_google():
    '''Does a google source produce expected results?'''
    name = 'some name'
    assert (parse_rules.get_file_id({'rules_source': get_rules.GOOGLE_SOURCE,
                                     'spreadsheet_name': name}) ==
            name)


# check_empty_row()

def test_raise_empty_row_full():
    '''A non-empty row fails to raise an exception'''
    parse_rules.check_empty_row(sample_config(), 2, ['one'], 0)
    assert True


def test_check_empty_row_empty():
    '''An empty row raises an exception'''
    with pytest.raises(ex.EmptyRowError):
        parse_rules.check_empty_row(sample_config(), 2, [''], 1)


# check_skipped_sub_dir()

def test_check_depth_delta_initial():
    '''First rule can start at top level'''
    parse_rules.check_skipped_sub_dir(sample_config(), 2, -1, 0)
    assert True


def test_check_skipped_sub_dir_initial_bad():
    '''First rule must start at top level'''
    with pytest.raises(ex.SkippedSubDirError):
        parse_rules.check_skipped_sub_dir(sample_config(), 2, -1, 1)


def test_check_skipped_sub_dir():
    '''Rules can go downward one level in directory tree'''
    parse_rules.check_skipped_sub_dir(sample_config(), 3, 0, 1)
    assert True


def test_check_skipped_sub_dir_upwards():
    '''Check that the rules can skip directries going upwards in the tree'''
    parse_rules.check_skipped_sub_dir(sample_config(), 10, 3, 0)
    assert True


# check_parent_with_child()

def test_check_parent_with_child_fail():
    '''A row with 2 non-empty cells raises an exception'''
    with pytest.raises(ex.ParentWithChildError):
        parse_rules.check_parent_with_child(
            sample_config(), 3, ['', 'two', 'three'], 1)


def test_check_parent_with_child():
    '''A row with only one non-empty cell does not raise an exception'''
    parse_rules.check_parent_with_child(
        sample_config(), 3, ['', 'two', ''], 1)


# get_depth()

def test_get_depth_emptystrings():
    '''An empty row has a depth of N-cells + 1'''
    assert parse_rules.get_depth(['', '', '']) == 3


def test_get_depth():
    '''A non-empty row can have the non-empty cell's column identified'''
    assert parse_rules.get_depth(['', 'one', '']) == 1


# lex_rules_fs_struct()

# (Lexer can't raise multiple errors at this time, so no way to test
# we'll get multiple errors.)

def test_lex_rules_fs_struct_empty():
    '''An empty rules file should raise an exception'''
    with pytest.raises(ex.NoMarkerError):
        parse_rules.lex_rules_fs_struct(sample_config(), [])


def test_lex_rules_fs_struct_no_marker():
    '''A rules file with no marker should raise an exception'''
    with pytest.raises(ex.NoMarkerError):
        parse_rules.lex_rules_fs_struct(
            sample_config(), ['one\n', 'two\n', 'three\n'])


def test_lex_rules_fs_struct_max_depth():
    '''max_depth is 4, ignore all subsequent columns'''
    rules = ['remark1\n', 'remark2\n', 'begin\n',
             'one\n', ',two,,,comment\n', ',,three\n']
    expected_results = [(4, 0, ['one']),
                        (5, 1, ['', 'two', '', '']),
                        (6, 2, ['', '', 'three'])]
    tokens = parse_rules.lex_rules_fs_struct(sample_config(), rules)
    assert tokens == expected_results


def test_lex_rules_fs_struct():
    '''Produce the right token structure'''
    rules = sample_rules()
    expected_results = [(4, 0, ['one']),
                        (5, 1, ['', 'two']),
                        (6, 2, ['', '', 'three']),
                        (7, 0, ['four'])]
    tokens = parse_rules.lex_rules_fs_struct(sample_config(), rules)
    assert tokens == expected_results


# raise_bad_row()

def test_raise_bad_row_exception():
    '''An bad row should return a BadRow exception.  Use an empty row.'''
    with pytest.raises(ex.BadRowError):
        parse_rules.raise_bad_row(
            sample_config(),
            fstree.FSDir(fstree.DecisionTree, 'pattern'),
            (1, 1, []), 0)


# validate_row()

def test_validate_row_false():
    '''A bad row where the exception is caught should return false.'''
    result = parse_rules.validate_row(
        startup.em,
        sample_config(),
        fstree.FSDir(fstree.DecisionTree, 'pattern'),
        (1, 1, []), 0)
    assert result is False
    # Clear out the global error state (!)
    clear_error_state()


def test_validate_row_true():
    '''A good row should return true.'''
    result = parse_rules.validate_row(
        startup.em,
        sample_config(),
        fstree.FSDir(fstree.DecisionTree, 'pattern'),
        (1, 0, ['one']), 0)
    assert result is True


# parse_rules_fs_struct()

# Check file structure of sample rules.

def test_parse_rules_fs_struct_no_fs_struct():
    with pytest.raises(ex.NoRulesError):
        parse_rules.parse_rules_fs_struct(
            fstree.DecisionTree, startup.em, sample_config(), [])


def test_parse_rules_fs_struct_rootcontent():
    '''Is the root node's content correct?'''
    root = parse_rules.parse_rules_fs_struct(
        libtest.MockTree, startup.em,
        sample_config(), sample_tokens(sample_rules()))
    # Root's content
    assert (root.decision_tree.tree ==
            [(4, 'one'), (7, 'four')])


def test_parse_rules_fs_struct_firstchild():
    '''Is the first dir correct?'''
    root = parse_rules.parse_rules_fs_struct(
        libtest.MockTree, startup.em,
        sample_config(), sample_tokens(sample_rules()))

    # Root's first child
    first_child = root.children[4]
    assert isinstance(first_child, fstree.FSDir)
    assert first_child.name_pattern == 'one'
    assert first_child.rownum == 4
    assert first_child.decision_tree.tree == [(5, 'two')]


def test_parse_rules_fs_struct_grandchild():
    '''Is the grandchild dir correct?'''
    root = parse_rules.parse_rules_fs_struct(
        libtest.MockTree, startup.em,
        sample_config(), sample_tokens(sample_rules()))

    # Root's first grandchild
    first_child = root.children[4]
    grandchild = first_child.children[5]
    assert isinstance(grandchild, fstree.FSDir)
    assert grandchild.name_pattern == 'two'
    assert grandchild.rownum == 5
    assert grandchild.decision_tree.tree == [(6, 'three')]


def test_parse_rules_fs_struct_error():
    '''A bad rule generates an error'''
    parse_rules.parse_rules_fs_struct(
        fstree.DecisionTree, startup.em, sample_config,
        sample_tokens(['remark1\n',
                       'begin\n',
                       'one\n',
                       ',two - (bad)\n',
                       ',,three - (bad)\n',
                       'four\n']))
    with pytest.raises(SystemExit):
        startup.em.exit_if_errored()


# Error handling
def test_parse_rules_fs_struct_errors(capsys):
    '''Do we get multiple errors when there are multiple problems?'''
    # Test with 2 empty rows
    parse_rules.parse_rules_fs_struct(
        libtest.MockTree, startup.em,
        sample_config(), [(1, 0, []), (2, 0, [])])
    (out, err) = capsys.readouterr()
    assert libtest.multiple_errors(err)
    clear_error_state()
