# -*- coding: utf-8 -*-
# Copyright 2017 - 2018 Avram Lubkin, All Rights Reserved

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
Test module for Enlighten
"""

from contextlib import contextmanager
import fcntl
import io
import os
import pty
import struct
import sys
import termios

from enlighten import Manager
from enlighten._basecounter import BaseCounter
from enlighten._counter import Counter
from enlighten._statusbar import StatusBar

# pylint: disable=import-error

if sys.version_info[:2] < (2, 7):
    import unittest2 as unittest
else:
    import unittest  # pylint: disable=wrong-import-order

if sys.version_info[:2] < (3, 3):
    import mock
else:
    from unittest import mock  # noqa: F401  # pylint: disable=no-name-in-module

if sys.version_info[0] < 3:
    from StringIO import StringIO
else:
    from io import StringIO

# pylint: enable=import-error


OUTPUT = StringIO()
os.environ['TERM'] = 'xterm-256color'  # Default to xterm-256color


# pylint: disable=missing-docstring

class TestCase(unittest.TestCase):
    """
    Subclass of :py:class:`unittest.TestCase` for customization
    """


# Fix deprecated methods for EL6
def assert_regex(self, text, regex, msg=None):
    """
    Wrapper for assertRegexpMatches
    """

    return self.assertRegexpMatches(text, regex, msg)


def assert_not_regex(self, text, regex, msg=None):
    """
    Wrapper for assertNotRegexpMatches
    """

    return self.assertNotRegexpMatches(text, regex, msg)


def assert_raises_regex(self, exception, regex, *args, **kwargs):
    """
    Wrapper for assertRaisesRegexp
    """

    return self.assertRaisesRegexp(exception, regex, *args, **kwargs)


if not hasattr(TestCase, 'assertRegex'):
    TestCase.assertRegex = assert_regex

if not hasattr(TestCase, 'assertNotRegex'):
    TestCase.assertNotRegex = assert_not_regex

if not hasattr(TestCase, 'assertRaisesRegex'):
    TestCase.assertRaisesRegex = assert_raises_regex


# Some tests fail if "real" stdout is does not have a file descriptor
try:
    sys.__stdout__.fileno()
except ValueError:
    STDOUT_NO_FD = True
else:
    STDOUT_NO_FD = False


@contextmanager
def redirect_output(stream, target):
    """
    Temporary redirector for stdout and stderr
    """

    original = getattr(sys, stream)
    try:
        setattr(sys, stream, target)
        yield
    finally:
        setattr(sys, stream, original)


class MockTTY(object):

    def __init__(self, height=25, width=80):

        self.master, self.slave = pty.openpty()

        if sys.version_info[:2] < (2, 7):
            self.stdout = os.fdopen(self.slave, 'w', 1)
            self.stdread = os.fdopen(self.master, 'r')
        else:
            self.stdout = io.open(self.slave, 'w', 1, encoding='UTF-8', newline='')
            self.stdread = io.open(self.master, 'r', encoding='UTF-8', newline='\n')

        # Make sure linefeed behavior is consistent between Python 2 and Python 3
        termattrs = termios.tcgetattr(self.slave)
        termattrs[1] = termattrs[1] & ~termios.ONLCR & ~termios.OCRNL
        termattrs[0] = termattrs[0] & ~termios.ICRNL
        termios.tcsetattr(self.slave, termios.TCSADRAIN, termattrs)

        self.resize(height, width)

    def flush(self):
        self.stdout.flush()

    def close(self):
        self.stdout.flush()
        self.stdout.close()
        self.stdread.close()

    def clear(self):
        termios.tcflush(self.stdread, termios.TCIFLUSH)

    def resize(self, height, width):
        fcntl.ioctl(self.slave, termios.TIOCSWINSZ, struct.pack('hhhh', height, width, 0, 0))


class MockBaseCounter(BaseCounter):
    """
    Mock version of base counter for testing
    """

    def update(self, *args, **kwargs):
        """
        Simple update that updates the count. We know it's called based on the count.
        """

        self.count += 1


class MockCounter(Counter):

    __slots__ = ('output', 'calls')

    def __init__(self, *args, **kwargs):
        super(MockCounter, self).__init__(*args, **kwargs)
        self.output = []
        self.calls = []

    def refresh(self, flush=True, elapsed=None):
        self.output.append(self.count)
        self.calls.append('refresh(flush=%s, elapsed=%s)' % (flush, elapsed))

    def clear(self, flush=True):
        self.calls.append('clear(flush=%s)' % flush)


class MockStatusBar(StatusBar):

    __slots__ = ('called', 'calls')

    def __init__(self, *args, **kwargs):
        super(MockStatusBar, self).__init__(*args, **kwargs)
        self.called = 0
        self.calls = []

    def refresh(self, flush=True, elapsed=None):
        self.called += 1
        self.calls.append('refresh(flush=%s, elapsed=%s)' % (flush, elapsed))


class MockManager(Manager):
    # pylint: disable=super-init-not-called
    def __init__(self, counter_class=Counter, **kwargs):
        super(MockManager, self).__init__(counter_class=counter_class, **kwargs)
        self.width = 80
        self.output = []
        self.remove_calls = 0

    def write(self, output='', flush=True, counter=None):
        self.output.append('write(output=%s, flush=%s, position=%s)' %
                           (output, flush, counter.position))

    def remove(self, counter):
        self.remove_calls += 1
        super(MockManager, self).remove(counter)
