# 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>

'''
Parse and compile file structure conventions.
'''

import csv

from . import exceptions as ex
from . import get_rules
from . import fstree
from . import rules_yacc


#
# Initialization
#
parser = rules_yacc.parser


#
# Functions
#
def get_file_id(config):
    if config['rules_source'] == get_rules.FILE_SOURCE:
        return config['rules_path']
    else:
        return config['spreadsheet_name']


def check_empty_row(config, rownum, row, depth):
    if len(row) <= depth:
        raise ex.EmptyRowError('Row {0} of file {1} has no data'
                               .format(rownum, get_file_id(config)))


def check_skipped_sub_dir(config, rownum, cur_depth, new_depth):
    if new_depth - cur_depth > 1:
        raise ex.SkippedSubDirError(
            'Row {0} of file {1} skips a directory level'
            .format(rownum, get_file_id(config)))


def check_parent_with_child(config, rownum, row, new_depth):
    for cell in row[new_depth + 1:]:
        if cell != '':
            raise ex.ParentWithChildError(
                'Row {0} of file {1} has 2 non-empty cells'
                .format(rownum, get_file_id(config)))


def get_depth(row):
    '''First non-empty cell is the (zero based) depth.'''
    depth = 0
    for cell in row:
        if cell != '':
            return depth
        depth += 1
    return depth


def lex_rules_fs_struct(config, rule_file):
    '''Return tokens representing the fs structure in the rules file.'''
    rule_reader = csv.reader(rule_file)
    marker_seen = False
    rownum = 1          # one based
    fs_tokens = []
    for row in rule_reader:
        if not marker_seen:
            if row[0] == config['begin_marker']:
                marker_seen = True
        else:
            row = row[0:config['max_depth']]
            depth = get_depth(row)

            fs_tokens.append((rownum, depth, row))

        rownum += 1
    if not marker_seen:
        raise ex.NoMarkerError(
            ('The beginning of rule marker "{0}" is not in the'
             ' storage conventions rule file "{1}"')
            .format(config['begin_marker'], get_file_id(config)))

    return fs_tokens


def raise_bad_row(config, parent_dir, fs_token, last_depth):
    '''Validate nested directory properties of the rules'''
    (rownum, depth, row) = fs_token
    check_empty_row(config, rownum, row, depth)
    # Because we start the depth at -1 this ensures that the
    # rule file starts at depth 0 as well as ensuring that the
    # new depth is within +1/-N of current depth.
    check_skipped_sub_dir(config, rownum, last_depth, depth)
    check_parent_with_child(config, rownum, row, last_depth)


def validate_row(em, config, parent_dir, fs_token, last_depth):
    '''Validate nested directory properties of the rules'''
    (rownum, depth, row) = fs_token
    try:
        raise_bad_row(config, parent_dir, fs_token, last_depth)
    except ex.BadRowError as err:
        em.express(err)
        return False
    else:
        return True


def parse_rules_fs_struct(tree_class, em, config, fs_tokens):
    if not fs_tokens:
        raise ex.NoRulesError()

    root_dir = fstree.FSDir(tree_class, name_pattern='.')
    root_dir.set_root()

    parent_dir = root_dir
    last_depth = -1
    prior_token = None
    for fs_token in fs_tokens:
        (rownum, depth, row) = fs_token
        if validate_row(em, config, parent_dir, fs_token, depth):
            if depth > last_depth:
                if prior_token:
                    # Prior token is a dir
                    (prior_rownum, prior_depth, prior_row) = prior_token
                    new_dir = fstree.FSDir(tree_class,
                                           name_pattern=prior_row[prior_depth],
                                           parent=parent_dir,
                                           rownum=prior_rownum)
                    parent_dir.add_child(prior_rownum, new_dir)
                    parent_dir = new_dir
            elif depth < last_depth:
                for step in range(last_depth - depth):
                    parent_dir = parent_dir.parent

            try:
                parent_dir.add_content(
                    parent_dir.decision_tree, parser, rownum, row[depth])
            except ex.RuleError as err:
                em.express(err)

            prior_token = fs_token
            last_depth = depth

    return root_dir


def compile(em, config, rule_file):
    fs_tokens = lex_rules_fs_struct(config, rule_file)
    em.exit_if_errored()
    analyzer = parse_rules_fs_struct(
        fstree.DecisionTree, em, config, fs_tokens)
    return analyzer
