""" ESC/POS configuration manager.

This module contains the implentations of abstract base class :py:class:`Config`.

"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import os
import appdirs
import yaml

from . import printer
from . import exceptions


class Config(object):
    """  Configuration handler class.

    This class loads configuration from a default or specificed directory. It
    can create your defined printer and return it to you.
    """
    _app_name = 'python-escpos'
    _config_file = 'config.yaml'

    def __init__(self):
        """ Initialize configuration.

        Remember to add anything that needs to be reset between configurations
        to self._reset_config
        """
        self._has_loaded = False
        self._printer = None

        self._printer_name = None
        self._printer_config = None

    def _reset_config(self):
        """ Clear the loaded configuration.

        If we are loading a changed config, we don't want to have leftover
        data.
        """
        self._has_loaded = False
        self._printer = None

        self._printer_name = None
        self._printer_config = None

    def load(self, config_path=None):
        """ Load and parse the configuration file using pyyaml

        :param config_path: An optional file path, file handle, or byte string
            for the configuration file.

        """

        self._reset_config()

        if not config_path:
            config_path = os.path.join(
                appdirs.user_config_dir(self._app_name),
                self._config_file
            )

        try:
            # First check if it's file like. If it is, pyyaml can load it.
            # I'm checking type instead of catching exceptions to keep the
            # exception handling simple
            if hasattr(config_path, 'read'):
                config = yaml.safe_load(config_path)
            else:
                # If it isn't, it's a path. We have to open it first, otherwise
                # pyyaml will try to read it as yaml
                with open(config_path, 'rb') as config_file:
                    config = yaml.safe_load(config_file)
        except EnvironmentError:
            raise exceptions.ConfigNotFoundError('Couldn\'t read config at {config_path}'.format(
                config_path=str(config_path),
            ))
        except yaml.YAMLError:
            raise exceptions.ConfigSyntaxError('Error parsing YAML')

        if 'printer' in config:
            self._printer_config = config['printer']
            self._printer_name = self._printer_config.pop('type').title()

            if not self._printer_name or not hasattr(printer, self._printer_name):
                raise exceptions.ConfigSyntaxError(
                    'Printer type "{printer_name}" is invalid'.format(
                        printer_name=self._printer_name,
                    )
                )

        self._has_loaded = True

    def printer(self):
        """ Returns a printer that was defined in the config, or throws an
        exception.

        This method loads the default config if one hasn't beeen already loaded.

        """
        if not self._has_loaded:
            self.load()

        if not self._printer_name:
            raise exceptions.ConfigSectionMissingError('printer')

        if not self._printer:
            # We could catch init errors and make them a ConfigSyntaxError,
            # but I'll just let them pass
            self._printer = getattr(printer, self._printer_name)(**self._printer_config)

        return self._printer
