from distutils.dir_util import copy_tree
import logging
import os
import platform
import shutil
import subprocess
import tempfile
from typing import Dict, Optional, Union
from urllib.request import urlretrieve
from zipfile import ZipFile

import apertium


class Windows:
    """Download ApertiumWin64 and Move to %localappdata%"""
    base_link = 'http://apertium.projectjj.com/{}'

    def __init__(self) -> None:
        self._install_path = str(os.getenv('LOCALAPPDATA'))  # type: str
        self._apertium_path = str(os.path.join(self._install_path, 'apertium-all-dev'))  # type: str
        self._download_path = tempfile.mkdtemp()
        logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
        self._logger = logging.getLogger()
        self._logger.setLevel(logging.DEBUG)

    def _download_zip(self, download_files: Dict[str, str], extract_path: Optional[str]) -> None:
        for zip_name, zip_link in download_files.items():
            zip_download_path = os.path.join(self._download_path, zip_name)
            urlretrieve(Windows.base_link.format(zip_link), filename=zip_download_path)
            self._logger.info('%s -> %s download completed', Windows.base_link.format(zip_link), zip_name)

            # Extract the zip
            with ZipFile(zip_download_path) as zip_file:
                zip_file.extractall(path=extract_path)
            self._logger.info('%s Extraction completed', zip_name)
            os.remove(zip_download_path)
            self._logger.info('%s removed', zip_name)

    def _download_package(self, package: str) -> None:
        """Installs Packages to %localappdata%/Apertium"""

        zip_path = 'win32/nightly/data.php?zip='
        package_zip = {package: zip_path + package}
        self._download_zip(package_zip, self._download_path)

        # move the extracted files to desired location
        lang_data_path = os.path.join(self._download_path, 'usr', 'share', 'apertium')

        self._logger.info('Copying Language Data to Apertium')
        for directory in os.listdir(lang_data_path):
            source = str(os.path.join(lang_data_path, directory))  # type: str
            destination = str(os.path.join(self._apertium_path, 'share', 'apertium', directory))  # type: str
            copy_tree(source, destination)
            self._logger.info('%s -> %s', source, destination)

        shutil.rmtree(os.path.join(self._download_path, 'usr'))

    def _edit_modes(self) -> None:
        r"""The mode files need to be modified before being used on Windows System

        1. Replace /usr/share with %localappdata%\apertium-all-dev\share
        2. Replace "/" with "\" to make path compatible with Windows System
        """

        # List of Mode Files
        mode_path = str(os.path.join(self._apertium_path, 'share', 'apertium', 'modes'))  # type: str
        for f in os.listdir(mode_path):
            if os.path.isfile(os.path.join(mode_path, f)) and f.endswith('.mode'):
                self._logger.info('Editing mode %s ', f)
                with open(os.path.join(mode_path, f)) as infile:
                    line = infile.read()

                contents = line.split(' ')
                # Editing mode file to be compatible with windows platform
                for i, t in enumerate(contents):
                    if len(t) > 2 and t[0] == "'" and t[1] == '/':
                        t = t.replace('/', os.sep)
                        t = t.replace(r'\usr', self._apertium_path)
                        contents[i] = t
                line = ' '.join(contents)
                with open(os.path.join(mode_path, f), 'w') as outfile:
                    outfile.write(line)
                    outfile.close()

    def install_apertium_base(self) -> None:
        """Installs Apertium-all-dev to %localappdata%"""

        apertium_windows = {
            'apertium-all-dev.zip': '/win64/nightly/apertium-all-dev.zip',
        }

        self._download_zip(apertium_windows, self._install_path)

    def install_apertium_module(self, language: str) -> None:
        self._download_package(language)
        self._edit_modes()

    def install_wrapper(self, swig_wrapper: str) -> None:
        # TODO: create installer for wrappers on windows
        pass


class Ubuntu:
    @staticmethod
    def _install_package_source() -> None:
        install_script_url = 'http://apertium.projectjj.com/apt/install-nightly.sh'
        with tempfile.NamedTemporaryFile('w') as install_script:
            urlretrieve(install_script_url, install_script.name)
            execute = subprocess.run(['sudo', 'bash', install_script.name])
            execute.check_returncode()

    @staticmethod
    def _download_package(package: str) -> None:
        command = ['sudo', 'apt-get', 'install', '-y', package]
        execute = subprocess.run(command)
        execute.check_returncode()

    @staticmethod
    def _rename_wrappers() -> None:
        wrapper_name = {
            'python3-apertium': '_apertium_core',
            'python3-apertium-lex-tools': '_lextools',
            'python3-cg3': '_constraint_grammar',
            'python3-lttoolbox': '_lttoolbox',
        }
        dist_package = '/usr/lib/python3/dist-packages'
        for wrapper in wrapper_name.values():
            for f in os.listdir(dist_package):
                if f.startswith(wrapper):
                    old_name = os.path.join(dist_package, f)
                    new_name = os.path.join(dist_package, '{}.so'.format(f.split('.')[0]))
                    if old_name != new_name:
                        subprocess.run(['sudo', 'mv', old_name, new_name])

    def install_apertium_module(self, language: str) -> None:
        self._download_package(language)

    def install_apertium_base(self) -> None:
        self._install_package_source()
        self._download_package('apertium-all-dev')

    def install_wrapper(self, swig_wrapper: str) -> None:
        self._download_package(swig_wrapper)
        self._rename_wrappers()


def get_installer() -> Union[Windows, Ubuntu]:
    system = platform.system()  # type: str
    if system == 'Windows':
        return Windows()
    elif system == 'Linux':
        with open('/etc/os-release') as os_release:
            distro_name = os_release.readline().split('=')[-1].strip().replace('"', '')
        if distro_name == 'Ubuntu':
            return Ubuntu()
        else:
            raise apertium.InstallationNotSupported(distro_name)
    else:
        raise apertium.InstallationNotSupported(system)


def install_apertium() -> None:
    installer = get_installer()
    installer.install_apertium_base()


def install_module(module: str) -> None:
    apertium_module = 'apertium-{}'.format(module)
    installer = get_installer()  # type: Union[Windows, Ubuntu]
    installer.install_apertium_module(apertium_module)


def install_wrapper(swig_wrapper: str) -> None:
    installer = get_installer()  # type: Union[Windows, Ubuntu]
    installer.install_wrapper(swig_wrapper)


def install_apertium_linux() -> None:
    """
    Installs apertium-* packages on Linux Platforms
    """
    if platform.system() == 'Linux':
        install_module('anaphora')
