#
# Copyright 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
""" The file for testing the gr-modtool scripts """

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

import shutil
import tempfile
import unittest
import warnings
from os import path
from pylint.epylint import py_run

from gnuradio.modtool.core import *

class TestModToolCore(unittest.TestCase):
    """ The tests for the modtool core """
    def __init__(self, *args, **kwargs):
        super(TestModToolCore, self).__init__(*args, **kwargs)
        self.f_add = False
        self.f_newmod = False

    @classmethod
    def setUpClass(cls):
        """ create a temporary directory """
        cls.test_dir = tempfile.mkdtemp()

    @classmethod
    def tearDownClass(cls):
        """ remove the directory after the test """
        shutil.rmtree(cls.test_dir)

    def setUp(self):
        """ create a new module and block before every test """
        try:
            warnings.simplefilter("ignore", ResourceWarning)
            args = {'module_name':'howto', 'directory': self.test_dir}
            ModToolNewModule(**args).run()
        except (TypeError, ModToolException):
            self.f_newmod = True
        else:
            try:
                args = {'blockname':'square_ff', 'block_type':'general',
                        'lang':'cpp', 'directory': self.test_dir + '/gr-howto',
                        'add_python_qa': True}
                ModToolAdd(**args).run()
            except (TypeError, ModToolException):
                self.f_add = True

    def tearDown(self):
        """ removes the created module """
        # Required, else the new-module directory command
        # in setup will throw exception after first test
        ## cannot remove if directory is not created
        if not self.f_newmod:
            rmdir = self.test_dir + '/gr-howto'
            shutil.rmtree(rmdir)

    def test_newmod(self):
        """ Tests for the API function newmod """
        ## Tests for proper exceptions ##
        test_dict = {}
        test_dict['directory'] = self.test_dir
        # module name not specified
        self.assertRaises(ModToolException, ModToolNewModule(**test_dict).run)
        test_dict['module_name'] = 'howto'
        # expected module_name as a string instead of dict
        self.assertRaises(TypeError, ModToolNewModule(test_dict).run)
        # directory already exists
        # will not be raised if the command in setup failed
        self.assertRaises(ModToolException, ModToolNewModule(**test_dict).run)

        ## Some tests for checking the created directory, sub-directories and files ##
        test_dict['module_name'] = 'test'
        ModToolNewModule(**test_dict).run()
        module_dir = path.join(self.test_dir, 'gr-test')
        self.assertTrue(path.isdir(module_dir))
        self.assertTrue(path.isdir(path.join(module_dir, 'lib')))
        self.assertTrue(path.isdir(path.join(module_dir, 'python')))
        self.assertTrue(path.isdir(path.join(module_dir, 'include')))
        self.assertTrue(path.isdir(path.join(module_dir, 'docs')))
        self.assertTrue(path.isdir(path.join(module_dir, 'cmake')))
        self.assertTrue(path.isdir(path.join(module_dir, 'swig')))
        self.assertTrue(path.exists(path.join(module_dir, 'CMakeLists.txt')))

        ## pylint tests ##
        python_dir = path.join(module_dir, 'python')
        py_module = path.join(python_dir, 'build_utils.py')
        (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only', return_std=True)
        print(pylint_stdout.getvalue(), end='')
        py_module = path.join(python_dir, 'build_utils_codes.py')
        (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only', return_std=True)
        print(pylint_stdout.getvalue(), end='')

        ## The check for object instantiation ##
        test_obj = ModToolNewModule()
        # module name not specified
        self.assertRaises(ModToolException, test_obj.run)
        test_obj.info['modname'] = 'howto'
        test_obj.directory = self.test_dir
        # directory already exists
        self.assertRaises(ModToolException, test_obj.run)
        test_obj.info['modname'] = 'test1'
        test_obj.run()
        self.assertTrue(path.isdir(self.test_dir+'/gr-test1'))
        self.assertTrue(path.isdir(self.test_dir+'/gr-test1/lib'))
        self.assertTrue(path.exists(self.test_dir+'/gr-test1/CMakeLists.txt'))

    def test_add(self):
        """ Tests for the API function add """
        ## skip tests if newmod command wasn't successful
        if self.f_newmod:
            raise unittest.SkipTest("setUp for API function 'add' failed")
        module_dir = path.join(self.test_dir, 'gr-howto')
        ## Tests for proper exceptions ##
        test_dict = {}
        test_dict['directory'] = module_dir
        # missing blockname, block_type, lang
        self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
        test_dict['blockname'] = 'add_ff'
        # missing arguments block_type, lang
        self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
        test_dict['block_type'] = 'general'
        # missing argument lang
        self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
        test_dict['lang'] = 'cxx'
        # incorrect language
        self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
        test_dict['lang'] = 'cpp'
        test_dict['add_cpp_qa'] = 'Wrong'
        # boolean is expected for add_cpp_qa
        self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
        test_dict['add_cpp_qa'] = True
        test_dict['block_type'] = 'generaleee'
        # incorrect block type
        self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
        test_dict['block_type'] = 'general'
        test_dict['skip_lib'] = 'fail'
        # boolean value is expected for skip_lib, fails in instantiation
        self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
        test_dict['skip_lib'] = True
        # missing relevant subdir
        self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)

        ## Some tests for checking the created directory, sub-directories and files ##
        test_dict['skip_lib'] = False
        ModToolAdd(**test_dict).run()
        self.assertTrue(path.exists(path.join(module_dir, 'lib', 'qa_add_ff.cc')))
        self.assertTrue(path.exists(path.join(module_dir, 'lib', 'add_ff_impl.cc')))
        self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_add_ff.block.yml')))
        self.assertTrue(path.exists(path.join(module_dir, 'include', 'howto', 'add_ff.h')))

        ## The check for object instantiation ##
        test_obj = ModToolAdd()
        test_obj.dir = module_dir
        # missing blocktype, lang, blockname
        self.assertRaises(ModToolException, test_obj.run)
        test_obj.info['blocktype'] = 'general'
        # missing lang, blockname
        self.assertRaises(ModToolException, test_obj.run)
        test_obj.info['lang'] = 'python'
        test_obj.info['blockname'] = 'mul_ff'
        test_obj.add_py_qa = True
        test_obj.run()
        self.assertTrue(path.exists(path.join(module_dir, 'python', 'mul_ff.py')))
        self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_mul_ff.py')))
        self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_mul_ff.block.yml')))

        ## pylint tests ##
        python_dir = path.join(module_dir, 'python')
        py_module = path.join(python_dir, 'mul_ff.py')
        (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only --disable=E0602', return_std=True)
        print(pylint_stdout.getvalue(), end='')
        py_module = path.join(python_dir, 'qa_mul_ff.py')
        (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only', return_std=True)
        print(pylint_stdout.getvalue(), end='')

    def test_rename(self):
        """ Tests for the API function rename """
        if self.f_newmod or self.f_add:
            raise unittest.SkipTest("setUp for API function 'rename' failed")

        module_dir = path.join(self.test_dir, 'gr-howto')
        test_dict = {}
        test_dict['directory'] = module_dir
        # Missing 2 arguments blockname, new_name
        self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
        test_dict['blockname'] = 'square_ff'
        # Missing argument new_name
        self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
        test_dict['new_name'] = '//#'
        # Invalid new block name!
        self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
        test_dict['new_name'] = None
        # New Block name not specified
        self.assertRaises(ModToolException, ModToolRename(**test_dict).run)

        ## Some tests for checking the renamed files ##
        test_dict['new_name'] = 'div_ff'
        ModToolRename(**test_dict).run()
        self.assertTrue(path.exists(path.join(module_dir, 'lib', 'div_ff_impl.h')))
        self.assertTrue(path.exists(path.join(module_dir, 'lib', 'div_ff_impl.cc')))
        self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_div_ff.py')))
        self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_div_ff.block.yml')))

        ## The check for object instantiation ##
        test_obj = ModToolRename()
        test_obj.info['oldname'] = 'div_ff'
        test_obj.info['newname'] = 'sub_ff'
        test_obj.run()
        self.assertTrue(path.exists(path.join(module_dir, 'lib', 'sub_ff_impl.h')))
        self.assertTrue(path.exists(path.join(module_dir, 'lib', 'sub_ff_impl.cc')))
        self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_sub_ff.py')))
        self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_sub_ff.block.yml')))

    def test_remove(self):
        """ Tests for the API function remove """
        if self.f_newmod or self.f_add:
            raise unittest.SkipTest("setUp for API function 'remove' failed")
        module_dir = path.join(self.test_dir, 'gr-howto')
        test_dict = {}
        # missing argument blockname
        self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
        test_dict['directory'] = module_dir
        self.assertRaises(ModToolException, ModToolRename(**test_dict).run)

        ## Some tests to check blocks are not removed with different blocknames ##
        test_dict['blockname'] = 'div_ff'
        ModToolRemove(**test_dict).run()
        self.assertTrue(path.exists(path.join(module_dir, 'lib', 'square_ff_impl.h')))
        self.assertTrue(path.exists(path.join(module_dir, 'lib', 'square_ff_impl.cc')))
        self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_square_ff.py')))
        self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_square_ff.block.yml')))

        ## Some tests for checking the non-existence of removed files ##
        test_dict['blockname'] = 'square_ff'
        ModToolRemove(**test_dict).run()
        self.assertTrue(not path.exists(path.join(module_dir, 'lib', 'square_ff_impl.h')))
        self.assertTrue(not path.exists(path.join(module_dir, 'lib', 'square_ff_impl.cc')))
        self.assertTrue(not path.exists(path.join(module_dir, 'python', 'qa_square_ff.py')))
        self.assertTrue(not path.exists(path.join(module_dir, 'grc', 'howto_square_ff.block.yml')))

    def test_makeyaml(self):
        """ Tests for the API function makeyaml """
        if self.f_newmod or self.f_add:
            raise unittest.SkipTest("setUp for API function 'makeyaml' failed")
        module_dir = path.join(self.test_dir, 'gr-howto')
        test_dict = {}
        # missing argument blockname
        self.assertRaises(ModToolException, ModToolMakeYAML(**test_dict).run)
        test_dict['directory'] = module_dir
        self.assertRaises(ModToolException, ModToolMakeYAML(**test_dict).run)

        ## Some tests to check if the command reuns ##
        test_dict['blockname'] = 'square_ff'
        ModToolMakeYAML(**test_dict).run()

    def test_disable(self):
        """ Tests for the API function disable """
        if self.f_newmod or self.f_add:
            raise unittest.SkipTest("setUp for API function 'disable' failed")
        module_dir = path.join(self.test_dir, 'gr-howto')
        test_dict = {}
        # missing argument blockname
        self.assertRaises(ModToolException, ModToolDisable(**test_dict).run)
        test_dict['directory'] = module_dir
        self.assertRaises(ModToolException, ModToolDisable(**test_dict).run)

        ## Some tests to check if the command reuns ##
        test_dict['blockname'] = 'square_ff'
        ModToolDisable(**test_dict).run()

if __name__ == '__main__':
    unittest.main()