"""
<Program>
  unittest_toolbox.py

<Author>
  Konstantin Andrianov.

<Started>
  March 26, 2012.

<Copyright>
  See LICENSE for licensing information.

<Purpose>
  Provides an array of various methods for unit testing.  Use it instead of
  actual unittest module.  This module builds on unittest module.
  Specifically, Modified_TestCase is a derived class from unittest.TestCase.
"""

import os
import random
import shutil
import string
import tempfile
import unittest


class Modified_TestCase(unittest.TestCase):  # pylint: disable=invalid-name
    """
    <Purpose>
      Provide additional test-setup methods to make testing
      of module's methods-under-test as independent as possible.

      If you want to modify setUp()/tearDown() do:
      class Your_Test_Class(modified_TestCase):
        def setUp():
          your setup modification
          your setup modification
          ...
          modified_TestCase.setUp(self)

    <Methods>
      make_temp_directory(self, directory=None):
        Creates and returns an absolute path of a temporary directory.

      make_temp_file(self, suffix='.txt', directory=None):
        Creates and returns an absolute path of an empty temp file.

      make_temp_data_file(self, suffix='', directory=None, data = junk_data):
        Returns an absolute path of a temp file containing some data.

      random_path(self, length = 7):
        Generate a 'random' path consisting of n-length strings of random chars.


      Static Methods:
      --------------
      Following methods are static because they technically don't operate
      on any instances of the class, what they do is: they modify class variables
      (dictionaries) that are shared among all instances of the class.  So
      it is possible to call them without instantiating the class.

      random_string(length=7):
        Generate a 'length' long string of random characters.
    """

    def setUp(self):
        self._cleanup = []

    def tearDown(self):
        for cleanup_function in self._cleanup:
            # Perform clean up by executing clean-up functions.
            try:
                # OSError will occur if the directory was already removed.
                cleanup_function()

            except OSError:
                pass

    def make_temp_directory(self, directory=None):
        """Creates and returns an absolute path of a directory."""
        prefix = self.__class__.__name__ + "_"
        temp_directory = tempfile.mkdtemp(prefix=prefix, dir=directory)

        def _destroy_temp_directory():
            shutil.rmtree(temp_directory)

        self._cleanup.append(_destroy_temp_directory)
        return temp_directory

    def make_temp_file(self, suffix=".txt", directory=None):
        """Creates and returns an absolute path of an empty file."""

        prefix = "tmp_file_" + self.__class__.__name__ + "_"
        temp_file = tempfile.mkstemp(
            suffix=suffix, prefix=prefix, dir=directory
        )

        def _destroy_temp_file():
            os.unlink(temp_file[1])

        self._cleanup.append(_destroy_temp_file)
        return temp_file[1]

    def make_temp_data_file(self, suffix="", directory=None, data="junk data"):
        """Returns an absolute path of a temp file containing data."""

        temp_file_path = self.make_temp_file(suffix=suffix, directory=directory)
        temp_file = (
            open(  # pylint: disable=unspecified-encoding,consider-using-with
                temp_file_path, "wt"
            )
        )
        temp_file.write(data)
        temp_file.close()

        return temp_file_path

    def random_path(self, length=7):
        """Generate a 'random' path consisting of random n-length strings."""

        rand_path = "/" + self.random_string(length)
        for _ in range(2):
            rand_path = os.path.join(rand_path, self.random_string(length))

        return rand_path

    @staticmethod
    def random_string(length=15):
        """Generate a random string of specified length."""

        rand_str = ""
        for _ in range(length):
            rand_str += random.choice("abcdefABCDEF" + string.digits)  # nosec

        return rand_str
