#
# Copyright 2013, 2018, 2019 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.
#
""" Remove blocks module """

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

import os
import re
import sys
import glob
import logging

from ..tools import remove_pattern_from_file, CMakeFileEditor
from .base import ModTool, ModToolException

logger = logging.getLogger(__name__)


class ModToolRemove(ModTool):
    """ Remove block (delete files and remove Makefile entries) """
    name = 'remove'
    description = 'Remove a block from a module.'

    def __init__(self, blockname=None, **kwargs):
        ModTool.__init__(self, blockname, **kwargs)
        self.info['pattern'] = blockname

    def validate(self):
        """ Validates the arguments """
        ModTool._validate(self)
        if not self.info['pattern'] or self.info['pattern'].isspace():
            raise ModToolException("Incorrect blockname (Regex)!")

    def run(self):
        """ Go, go, go! """
        # This portion will be covered by the CLI
        if not self.cli:
            self.validate()
        else:
            from ..cli import cli_input
        def _remove_cc_test_case(filename=None, ed=None):
            """ Special function that removes the occurrences of a qa*.cc file
            from the CMakeLists.txt. """
            if filename[:2] != 'qa':
                return
            if self.info['version'] == '37':
                (base, ext) = os.path.splitext(filename)
                if ext == '.h':
                    remove_pattern_from_file(self._file['qalib'],
                                             r'^#include "{}"\s*$'.format(filename))
                    remove_pattern_from_file(self._file['qalib'],
                                             r'^\s*s->addTest\(gr::{}::{}::suite\(\)\);\s*$'.format(
                                                    self.info['modname'], base)
                                            )
                    self.scm.mark_file_updated(self._file['qalib'])
                elif ext == '.cc':
                    ed.remove_value('list',
                                    r'\$\{CMAKE_CURRENT_SOURCE_DIR\}/%s' % filename,
                                    to_ignore_start='APPEND test_{}_sources'.format(self.info['modname']))
                    self.scm.mark_file_updated(ed.filename)
            elif self.info['version'] == '38':
                (base, ext) = os.path.splitext(filename)
                if ext == '.cc':
                    ed.remove_value(
                        'list', filename,
                        to_ignore_start='APPEND test_{}_sources'.format(self.info['modname']))
                    self.scm.mark_file_updated(ed.filename)
            else:
                filebase = os.path.splitext(filename)[0]
                ed.delete_entry('add_executable', filebase)
                ed.delete_entry('target_link_libraries', filebase)
                ed.delete_entry('GR_ADD_TEST', filebase)
                ed.remove_double_newlines()
                self.scm.mark_file_updated(ed.filename)

        def _remove_py_test_case(filename=None, ed=None):
            """ Special function that removes the occurrences of a qa*.{cc,h} file
            from the CMakeLists.txt and the qa_$modname.cc. """
            if filename[:2] != 'qa':
                return
            filebase = os.path.splitext(filename)[0]
            ed.delete_entry('GR_ADD_TEST', filebase)
            ed.remove_double_newlines()

        def _make_swig_regex(filename):
            filebase = os.path.splitext(filename)[0]
            pyblockname = filebase.replace(self.info['modname'] + '_', '')
            regexp = r'(^\s*GR_SWIG_BLOCK_MAGIC2?\({},\s*{}\);|^\s*.include\s*"({}/)?{}"\s*)'.format \
                    (self.info['modname'], pyblockname, self.info['modname'], filename)
            return regexp
        # Go, go, go!
        if not self.skip_subdirs['lib']:
            self._run_subdir('lib', ('*.cc', '*.h'), ('add_library', 'list'),
                             cmakeedit_func=_remove_cc_test_case)
        if not self.skip_subdirs['include']:
            incl_files_deleted = self._run_subdir(self.info['includedir'], ('*.h',), ('install',))
        if not self.skip_subdirs['swig']:
            swig_files_deleted = self._run_subdir('swig', ('*.i',), ('install',))
            for f in incl_files_deleted + swig_files_deleted:
                # TODO do this on all *.i files
                remove_pattern_from_file(self._file['swig'], _make_swig_regex(f))
                self.scm.mark_file_updated(self._file['swig'])
        if not self.skip_subdirs['python']:
            py_files_deleted = self._run_subdir('python', ('*.py',), ('GR_PYTHON_INSTALL',),
                                                cmakeedit_func=_remove_py_test_case)
            for f in py_files_deleted:
                remove_pattern_from_file(self._file['pyinit'], r'.*import\s+{}.*'.format(f[:-3]))
                remove_pattern_from_file(self._file['pyinit'], r'.*from\s+{}\s+import.*\n'.format(f[:-3]))
        if not self.skip_subdirs['grc']:
            self._run_subdir('grc', ('*.yml',), ('install',))

    def _run_subdir(self, path, globs, makefile_vars, cmakeedit_func=None):
        """ Delete all files that match a certain pattern in path.
        path - The directory in which this will take place
        globs - A tuple of standard UNIX globs of files to delete (e.g. *.yml)
        makefile_vars - A tuple with a list of CMakeLists.txt-variables which
                        may contain references to the globbed files
        cmakeedit_func - If the CMakeLists.txt needs special editing, use this
        """
        if self.cli:
            from ..cli import cli_input
        # 1. Create a filtered list
        files = []
        for g in globs:
            files = files + sorted(glob.glob("{}/{}".format(path, g)))
        files_filt = []
        logger.info("Searching for matching files in {}/:".format(path))
        for f in files:
            if re.search(self.info['pattern'], os.path.basename(f)) is not None:
                files_filt.append(f)
        if len(files_filt) == 0:
            logger.info("None found.")
            return []
        # 2. Delete files, Makefile entries and other occurrences
        files_deleted = []
        ed = CMakeFileEditor('{}/CMakeLists.txt'.format(path))
        yes = self.info['yes']
        for f in files_filt:
            b = os.path.basename(f)
            if not yes and self.cli:
                ans = cli_input("Really delete {}? [Y/n/a/q]: ".format(f)).lower().strip()
                if ans == 'a':
                    yes = True
                if ans == 'q':
                    sys.exit(0)
                if ans == 'n':
                    continue
            files_deleted.append(b)
            logger.info("Deleting {}.".format(f))
            self.scm.remove_file(f)
            os.unlink(f)
            logger.info("Deleting occurrences of {} from {}/CMakeLists.txt...".format(b, path))
            for var in makefile_vars:
                ed.remove_value(var, b)
            if cmakeedit_func is not None:
                cmakeedit_func(b, ed)
        ed.write()
        self.scm.mark_files_updated(('{}/CMakeLists.txt'.format(path)))
        return files_deleted
