"""Common base class for output writers."""

from imp import reload
import os

from . import cardformat
reload(cardformat)
from .cardformat import CardFormat
from . import utils

import smtk


class BaseWriter(object):
    """"""

    def __init__(self):
        self.base_indent = ''
        self.name_prefix = None  # needed to disambiguate atts for ThermoElastic
        self.scope = None

    def start_command(self, name, indent=None, blank_line=True):
        if indent is None:
            indent = self.base_indent
        if blank_line:
            self.scope.output.write('\n')
        self.scope.output.write('%s%s:\n' % (indent, name))
        self.scope.output.write('%s{\n' % indent)

    def finish_command(self, indent=None):
        if indent is None:
            indent = self.base_indent
        self.scope.output.write('%s}\n' % indent)

    def get_attribute(self, att_type):
        """Returns single attribute of specified type."""
        att_list = self.scope.sim_atts.findAttributes(att_type)
        if not att_list:
            raise Exception('Warning: no attributes of type %s' % att_type)
        elif len(att_list) > 1 and self.name_prefix is None:
            message = ' Warning: expected single attrbute of type %s but found %d' % \
            (att_type, len(att_list))
            raise Exception(message)
        elif len(att_list) > 1:
            match_list = list()
            for att in att_list:
                if att.name().startswith(self.name_prefix):
                    match_list.append(att)
            if not match_list:
                raise Exception('Warning: no attributes of type %s matches name prefix %s' % \
                    (att_type, self.name_prefix))
            elif len(match_list) > 1:
                template = 'Warning: expected single attribute of type %s to matche name prefix %s but found %d'
                raise Exception(template % (att_type, self.name_prefix, len(match_list)))
            else:
                return match_list[0]

        # (else)
        return att_list[0]


    def write_standard_instance_att(self, att_type, skip_list=[], silent_list=[], indent=None, keyword_table={}):
        """Writes instanced attribute with items in standard form.

        Attribute name must = command name.
        Items must be in standard form - see write_standard_item() for description
        """
        if indent is None:
            indent = self.base_indent

        att = self.get_attribute(att_type)
        self.start_command(att_type, indent=indent)
        self.write_standard_items(att, skip_list, silent_list, indent+'  ', keyword_table)
        self.finish_command(indent=indent)


    def write_standard_items(self, attribute,  skip_list=[], silent_list=[], indent=None, keyword_table={}):
        """Writes items contained in attribute, using standard form.

        See write_standard_item() for description of standard form.

        Args:
            skip_list: list of item names to ignore
            silent_list: list of item names to "pass through", that is, write
                their descendants but no the item itself
            keyword_table: dictionary mapping item name to keyword; only when they are different
        """
        if indent is None:
            indent = self.base_indent + '  '
        # print 'write_standard_items, attribute name %s' % attribute.name()
        # print 'silent_list %s ' % silent_list
        # print 'skip_list %s' % skip_list
        skip_set = set(skip_list)
        assert attribute is not None, 'attribute is None'
        for i in range(attribute.numberOfItems()):
            item = attribute.item(i)
            if item.name() in skip_set:
                continue
            self.write_standard_item(item, skip_list, silent_list, indent, keyword_table)


    def write_standard_item(self, attribute_item, skip_list=[], silent_list=[], indent=None, keyword_table={}):
        """Writes attribute item with standard formatting rules.

        Standard item formatting rules:
        * Default ACE3P keyword == item name, but can be overriden in keyword table
        * By default, group items are written; only their children
        * However, if a group item's definition is optional,
          and the group item is enabled, its name will be written
          with the value "on"
        * Group items must only have ONE subgroup

        Args:
            skip_list: list of item names to completely ignore
            silent_list: list of item names to "pass through", that is, write
                their descendants but not the item itself
            keyword_table: dictionary mapping item name to keyword

        Returns:
            boolean indicating whether or not item was written
        """
        if indent is None:
            indent = self.base_indent + '  '
        # print 'write_standard_item, name %s' % attribute_item.name()
        # print 'silent_list %s' % silent_list
        # print 'skip_list %s' % skip_list
        if attribute_item.name() in set(skip_list):
            return False

        if not utils.passes_categories(attribute_item, self.scope.categories):
            return False
        if not attribute_item.isEnabled():
            return False

        item_type = attribute_item.type()
        if item_type == smtk.attribute.Item.GroupType:
            if attribute_item.definition().isOptional():
                keyword = attribute_item.name()
                self.scope.output.write('%s%s: on\n' % (indent, keyword))

            n = attribute_item.numberOfItemsPerGroup()
            for i in range(n):
                child_item = attribute_item.item(0, i)
                self.write_standard_item(child_item, skip_list, silent_list, indent)
            return True

        # Write this item, unless in the set of do-not-write names
        if not attribute_item.name() in set(silent_list):
            name = attribute_item.name()
            keyword = keyword_table.get(name, name)
            CardFormat(keyword).write_item(self.scope, attribute_item, indent)

        # Check value types for active children
        if hasattr(attribute_item, 'numberOfActiveChildrenItems'):
            n = attribute_item.numberOfActiveChildrenItems()
            for i in range(n):
                child_item = attribute_item.activeChildItem(i)
                #print '%d Writing active child item %s' % (i, item.name())
                self.write_standard_item(child_item, skip_list, silent_list, indent)
        return True


    def use_file(self, file_item, name='FileItem'):
        """Checks that file_item is set to an existing file and adds to upload list.

        Args:
            file_Item: smtk.attribute.FileItem specifying the file
            name: (string) name to use in error messages
        Returns:
            A string representing the BASE filename (not the full path)
        """
        if not file_item.isEnabled():
            return None
        elif not file_item.isSet():
            return None

        path = file_item.value(0)
        if path == '':
            message = '%s enabled but no file specified' % name
            self.scope.logger.addError(message)
            raise Exception(message)
        if not os.path.exists(path):
            message = '%s file not found at: %s' % (name, os.path.abspath(path))
            self.scope.logger.addError(message)
            raise Exception(message)

        self.scope.files_to_upload.add(path)
        return os.path.basename(path)

    def setup_symlink(self):
        """Checks InputData attribute for user-specified input results."""
        self.scope.nersc_directory = None
        self.scope.symlink = None

        data_att = self.scope.sim_atts.findAttribute('InputData')
        if data_att is None:
            return

        source_item = data_att.findString('Source')
        if source_item is None or not source_item.isEnabled():
            return
        if source_item.value() == 'NERSCDirectory':
            path_item = source_item.findChild('NERSCDirectory', smtk.attribute.SearchStyle.IMMEDIATE_ACTIVE)
            if path_item is not None:
                self.scope.symlink = path_item.value()
                print('Set symlink to \"{}\"'.format(self.scope.symlink))

                remote_path = path_item.value(0).rstrip('/')
                self.scope.symlink = remote_path
                self.scope.nersc_directory = os.path.basename(remote_path)
