#
# Copyright 2013 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.
#
""" Base class for the modules """

import os
import re
from argparse import ArgumentParser, RawDescriptionHelpFormatter

from gnuradio import gr
from util_functions import get_modname
from scm import SCMRepoFactory

class ModToolException(BaseException):
    """ Standard exception for modtool classes. """
    pass

class ModTool(object):
    """ Base class for all modtool command classes. """
    name = 'base'
    description = None

    def __init__(self):
        self._subdirs = ['lib', 'include', 'python', 'swig', 'grc'] # List subdirs where stuff happens
        self._has_subdirs = {}
        self._skip_subdirs = {}
        self._info = {}
        self._file = {}
        for subdir in self._subdirs:
            self._has_subdirs[subdir] = False
            self._skip_subdirs[subdir] = False
        self._dir = None

    @staticmethod
    def setup_parser(parser):
        """Override in child class."""
        pass

    @staticmethod
    def setup_parser_block(parser):
        """Setup options specific for block manipulating modules."""
        parser.add_argument("blockname", nargs="?", metavar="BLOCK_NAME",
                help="Name of the block/module")

    @staticmethod
    def get_parser():
        """Init the option parser."""
        parser = ArgumentParser(
                description='Manipulate with GNU Radio modules source code tree. ' + \
                        'Call it withou options to run specified command interactively',
                        formatter_class=RawDescriptionHelpFormatter)
        parser.add_argument("-d", "--directory", default=".",
                help="Base directory of the module. Defaults to the cwd.")
        parser.add_argument("--skip-lib", action="store_true",
                help="Don't do anything in the lib/ subdirectory.")
        parser.add_argument("--skip-swig", action="store_true",
                help="Don't do anything in the swig/ subdirectory.")
        parser.add_argument("--skip-python", action="store_true",
                help="Don't do anything in the python/ subdirectory.")
        parser.add_argument("--skip-grc", action="store_true",
                help="Don't do anything in the grc/ subdirectory.")
        parser.add_argument("--scm-mode", choices=('yes', 'no', 'auto'),
                default=gr.prefs().get_string('modtool', 'scm_mode', 'no'),
                help="Use source control management [ yes | no | auto ]).")
        parser.add_argument("-y", "--yes", action="store_true",
                help="Answer all questions with 'yes'. This can overwrite and delete your files, so be careful.")
        return parser

    def setup(self, options):
        """ Initialise all internal variables, such as the module name etc. """
        self._dir = options.directory
        if not self._check_directory(self._dir):
            raise ModToolException('No GNU Radio module found in the given directory.')
        if hasattr(options, 'module_name') and options.module_name is not None:
            self._info['modname'] = options.module_name
        else:
            self._info['modname'] = get_modname()
        if self._info['modname'] is None:
            raise ModToolException('No GNU Radio module found in the given directory.')
        print "GNU Radio module name identified: " + self._info['modname']
        if self._info['version'] == '36' and (
                os.path.isdir(os.path.join('include', self._info['modname'])) or
                os.path.isdir(os.path.join('include', 'gnuradio', self._info['modname']))
                ):
            self._info['version'] = '37'
        if options.skip_lib or not self._has_subdirs['lib']:
            self._skip_subdirs['lib'] = True
        if options.skip_python or not self._has_subdirs['python']:
            self._skip_subdirs['python'] = True
        if options.skip_swig or self._get_mainswigfile() is None or not self._has_subdirs['swig']:
            self._skip_subdirs['swig'] = True
        if options.skip_grc or not self._has_subdirs['grc']:
            self._skip_subdirs['grc'] = True
        self._info['blockname'] = options.blockname
        self._setup_files()
        self._info['yes'] = options.yes
        self.options = options
        self._setup_scm()

    def _setup_files(self):
        """ Initialise the self._file[] dictionary """
        if not self._skip_subdirs['swig']:
            self._file['swig'] = os.path.join('swig',   self._get_mainswigfile())
        self._info['pydir'] = 'python'
        if os.path.isdir(os.path.join('python', self._info['modname'])):
            self._info['pydir'] = os.path.join('python', self._info['modname'])
        self._file['qalib']    = os.path.join('lib',    'qa_%s.cc' % self._info['modname'])
        self._file['pyinit']   = os.path.join(self._info['pydir'], '__init__.py')
        self._file['cmlib']    = os.path.join('lib',    'CMakeLists.txt')
        self._file['cmgrc']    = os.path.join('grc',    'CMakeLists.txt')
        self._file['cmpython'] = os.path.join(self._info['pydir'], 'CMakeLists.txt')
        if self._info['is_component']:
            self._info['includedir'] = os.path.join('include', 'gnuradio', self._info['modname'])
        elif self._info['version'] == '37':
            self._info['includedir'] = os.path.join('include', self._info['modname'])
        else:
            self._info['includedir'] = 'include'
        self._file['cminclude'] = os.path.join(self._info['includedir'], 'CMakeLists.txt')
        self._file['cmswig'] = os.path.join('swig', 'CMakeLists.txt')
        self._file['cmfind'] = os.path.join('cmake', 'Modules', 'howtoConfig.cmake')


    def _setup_scm(self, mode='active'):
        """ Initialize source control management. """
        if mode == 'active':
            self.scm = SCMRepoFactory(self.options, '.').make_active_scm_manager()
        else:
            self.scm = SCMRepoFactory(self.options, '.').make_empty_scm_manager()
        if self.scm is None:
            print "Error: Can't set up SCM."
            exit(1)

    def _check_directory(self, directory):
        """ Guesses if dir is a valid GNU Radio module directory by looking for
        CMakeLists.txt and at least one of the subdirs lib/, python/ and swig/.
        Changes the directory, if valid. """
        has_makefile = False
        try:
            files = os.listdir(directory)
            os.chdir(directory)
        except OSError:
            print "Can't read or chdir to directory %s." % directory
            return False
        self._info['is_component'] = False
        for f in files:
            if os.path.isfile(f) and f == 'CMakeLists.txt':
                if re.search('find_package\(Gnuradio', open(f).read()) is not None:
                    self._info['version'] = '36' # Might be 37, check that later
                    has_makefile = True
                elif re.search('GR_REGISTER_COMPONENT', open(f).read()) is not None:
                    self._info['version'] = '36' # Might be 37, check that later
                    self._info['is_component'] = True
                    has_makefile = True
            # TODO search for autofoo
            elif os.path.isdir(f):
                if (f in self._has_subdirs.keys()):
                    self._has_subdirs[f] = True
                else:
                    self._skip_subdirs[f] = True
        return bool(has_makefile and (self._has_subdirs.values()))

    def _get_mainswigfile(self):
        """ Find out which name the main SWIG file has. In particular, is it
            a MODNAME.i or a MODNAME_swig.i? Returns None if none is found. """
        modname = self._info['modname']
        swig_files = (modname + '.i',
                      modname + '_swig.i')
        for fname in swig_files:
            if os.path.isfile(os.path.join(self._dir, 'swig', fname)):
                return fname
        return None

    def run(self, options):
        """ Override this. """
        raise NotImplementedError('Module implementation missing')


def get_modtool_modules(all_objects):
    """Return list with all modtool modules."""
    modules = []
    for o in all_objects:
        try:
            if issubclass(o, ModTool) and o != ModTool:
                modules.append(o)
        except (TypeError, AttributeError):
            pass
    return modules