"""
 *
 * This file is part of rasdaman community.
 *
 * Rasdaman community 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 of the License, or
 * (at your option) any later version.
 *
 * Rasdaman community 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 rasdaman community.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2003 - 2016 Peter Baumann / rasdaman GmbH.
 *
 * For more information please see <http://www.rasdaman.org>
 * or contact Peter Baumann via <baumann@rasdaman.com>.
 *
"""
from __future__ import print_function, unicode_literals
import fileinput
import os
import stat
import pwd
import re
from util.log import log
from util.string_util import strip_whitespace


def replace_in_file(path, search_replace_dict):
    """
    Replaces strings in a text file
    :param str path: the path to the file you want to replace in
    :param dict[str,str] search_replace_dict: the dict of
    pattern and replacement pairs
    """
    if not os.path.exists(path):
        log.warn("File '" + path + "' not found, cannot be updated.")
        return
    # with inplace=True, the file is moved to path.bak, and stdout output
    # with print is directed to the input file path
    for line in fileinput.input(path, inplace=True):
        for key, val in search_replace_dict.items():
            line = line.replace(key, val)
        print(line.replace("\n", ""))


def replace_in_file_matching_lines_re(path, pattern, pattern_replace_dict):
    """
    For lines matching the given pattern, replace regex patterns with values from
    the pattern_replace_dict.
    :param str path: the path to the file you want to replace in
    :param str pattern: the search_replace_dict will be applied only to lines
    matching the regex pattern
    :param dict[str,str] pattern_replace_dict: the dict of regex pattern and
    replacement pairs
    """
    if not os.path.exists(path):
        log.warn("File '" + path + "' not found, cannot be updated.")
        return
    # with inplace=True, the file is moved to path.bak, and stdout output
    # with print is directed to the input file path
    try:
        for line in fileinput.input(path, inplace=True):
            if re.match(pattern, line):
                for key, val in pattern_replace_dict.items():
                    line = re.sub(key, val, line)
            print(line.replace("\n", ""))
    except Exception as e:
        log.warn("Failed replacing lines matching {} in file {}: {}".format(
            pattern, path, e))


def get_all_filepaths(directory, wanted_filename=None):
    """
    Returns all the filepaths in a directory and all its subdirectories recursively,
    optionally matching the specified file name.
    :param str directory: the directory to retrieve the file paths for
    :param str wanted_filename: if specified, only the files equal to this parameter are included
    :rtype: list[str]
    """
    file_paths = []
    if not os.path.exists(directory):
        log.warn("Directory '" + directory + "' not found, cannot list the files in it.")
        return file_paths
    for root, directories, files in os.walk(directory):
        for filename in files:
            if wanted_filename is None or filename == wanted_filename:
                filepath = os.path.join(root, filename)
                file_paths.append(filepath)
    return file_paths


def get_all_filepaths_with_extension(directory, wanted_extension):
    """
    Returns all the filepaths in a directory and all its subdirectories recursively,
    matching the specified file extension.
    :param str directory: the directory to retrieve the file paths for
    :param str wanted_extension: files with this extensions are returned
    :rtype: list[str]
    """
    file_paths = []
    if not os.path.exists(directory):
        log.warn("Directory '" + directory + "' not found, cannot list the files in it.")
        return file_paths
    for root, directories, files in os.walk(directory):
        for filename in files:
            if filename.endswith(wanted_extension):
                filepath = os.path.join(root, filename)
                file_paths.append(filepath)
    return file_paths


def get_all_dirpaths(directory, wanted_dirname=None):
    """
    Returns all the directory paths in a directory and all its subdirectories recursively,
    optionally matching the specified directory name.
    :param str directory: the directory to retrieve the dir paths for
    :param str wanted_dirname: if specified, only the dirs equal to this parameter are included
    :rtype: list[str]
    """
    dir_paths = []
    if not os.path.exists(directory):
        log.warn("Directory '" + directory + "' not found, cannot list the directories in it.")
        return dir_paths
    for root, directories, files in os.walk(directory):
        for dirname in directories:
            if wanted_dirname is None or dirname == wanted_dirname:
                dirpath = os.path.join(root, dirname)
                dir_paths.append(dirpath)
    return dir_paths


def get_file_paths_by_regex(current_dir, file_path_regex):
    """
    From the file path in regular expression (e.g: *.txt, ./txt), return list of file paths
    :return: list of string
    """
    import sys
    if sys.version_info[0] < 3:
        # Python 2.x
        import glob2 as glob
    else:
        # Python 3.x
        import glob

    if not file_path_regex.strip().startswith("/"):
        file_path_regex = current_dir + file_path_regex
    return glob.glob(file_path_regex)


def find_line_containing_token_in_file(path, token):
    """
    Finds the first line that contains the given token and returns the line or None if nothing was found
    :param str path: the path to the file
    :param str token: the token to search for
    :rtype: str
    """
    ret = None
    if not os.path.exists(path):
        log.warn("File '" + path + "' not found, cannot search for token in it.")
        return ret
    file = open(path)
    lines = file.readlines()
    for line in lines:
        if token in line:
            ret = line
            break
    file.close()
    return ret


def find_line_starting_with(path, token):
    """
    Finds the first line that starts with the given token and returns the line or None if nothing was found
    :param str path: the path to the file
    :param str token: the token to search for
    :rtype: str
    """
    ret = None
    if not os.path.exists(path):
        log.warn("File '" + path + "' not found, cannot search for line starting with a token in it.")
        return ret
    file = open(path)
    lines = file.readlines()
    for line in lines:
        if line.startswith(token):
            ret = line
            break
    file.close()
    return ret


def get_values_for_property_keys(path, key_list):
    """
    Return a list of values corresponding to each of the keys in `key_list`,
    as found in a properties file at `path`, with settings in `key=value` format.
    The value for the last occurrence of each key is returned.

    :param str path: Path to the property file in key=value format.
    :param list[str] key_list: List of keys to look up.
    :returns list[str]: List of values for each of the specified keys
        (None value if a corresponding key was not found).
        If the file is not found, an empty list is returned.
    """
    ret = []
    if not os.path.exists(path):
        log.warn("File '" + path + "' not found, cannot search for properties in it.")
        return ret
    file = open(path)
    lines = file.readlines()
    for key in key_list:
        value = None
        for line in lines:
            if line.startswith(key + "="):
                value = strip_whitespace(line.split("=")[1])
        ret.append(value)
    file.close()
    return ret


def write_to_file(path, contents):
    """
    Write the given string contents to the specified file.
    :param str path: the path to the file
    :param str contents: the data to write in the file
    """
    try:
        with open(path, "w") as f:
            f.write(contents)
    except Exception as e:
        log.warn("Failed writing to file '" + path + "': " + str(e))


def append_to_file(path, contents):
    """
    Append the given string contents to the end of the specified file.
    :param str path: the path to the file
    :param str contents: the data to append in the file
    """
    try:
        with open(path, "a") as f:
            f.write(contents)
    except Exception as e:
        log.warn("Failed appending to file '" + path + "': " + str(e))


def read_file(path):
    """
    Return the contents of the specified file
    :param str path: the path to the file
    :@return the data contents, or None in case of error; Exception in case the
    file is not readable.
    :rtype: str
    """
    if not os.path.exists(path):
        log.warn("File '" + path + "' not found, cannot read its contents.")
        return None
    if not os.access(path, os.R_OK):
        import getpass
        log.warn("File '" + path + "' not readable by user '" + getpass.getuser() + "'.")
        raise Exception("File '" + path + "' not readable by user '" + getpass.getuser() + "'.")
    with open(path, "r") as f:
        return f.read()


def system_user_exists(username):
    try:
        pwd.getpwnam(username)
        return True
    except KeyError:
        return False


def system_group_exists(groupname):
    import grp
    try:
        grp.getgrnam(groupname)
        return True
    except KeyError:
        return False


def fix_trailing_slash(path):
    """
    Return the path with a "/" appended if it isn't already
    """
    if path is None or len(path) == 0:
        return ""
    if path[-1] == "/":
        return path
    else:
        return path + "/"


def remove_trailing_slash(path):
    """
    Remove trailing slash / if path has it, otherwise return path as is.
    """
    if path[-1] == "/":
        return path[0:-1]
    else:
        return path


def get_directory_path(file_path):
    return os.path.dirname(os.path.abspath(file_path))


def get_owner(path):
    """
    :return: the owner (username) of given path, or "" in case of error or path
    doesn't exist.
    """
    try:
        if os.path.exists(path):
            stat_info = os.stat(path)
            uid = stat_info.st_uid
            return pwd.getpwuid(uid).pw_name
        return ""
    except:
        return ""


def remove_files(files_list):
    """
    Remove the files in the given file_list.
    """
    for f in files_list:
        try:
            os.remove(f)
        except:
            log.warn("Failed removing file " + f)


def get_script_dir(script_file_path):
    """
    This function should be called with __file__ as a parameter.
    @param script_file_path: __file__
    @return: The directory in which the passed script_file_path is, ending with
    a '/'. E.g. /my/dir/module.py -> /my/dir/
    """
    return fix_trailing_slash(os.path.dirname(os.path.realpath(script_file_path)))


def get_filename(path):
    """
    @return: path without the directory and extension.
    """
    return os.path.basename(os.path.splitext(path)[0])


def is_dir_empty(dir_path):
    return not os.path.isdir(dir_path) or not any(os.scandir(dir_path))


def is_file_empty(fpath):
    return os.path.isfile(fpath) and os.path.getsize(fpath) == 0


def get_cpu_count():
    return len(os.sched_getaffinity(0))


class TmpFile:
    def __init__(self):
        """
        A utility function to do most of the repetitive work
        """
        from config_manager import ConfigManager
        self.tmp_path = ConfigManager.tmp_directory
        pass

    def generate_tmp_path(self, ftype="data"):
        """
        Generates a tmp unique path
        :param str ftype: the type of the file
        :rtype str
        """
        import uuid
        tmp_path = self.tmp_path + str(uuid.uuid4()).replace("-", "_") + "." + ftype
        return tmp_path

    def write_to_tmp_file(self, contents, ftype="gml"):
        """
        Writes a string to a temporary file and returns the path to it
        :param str contents: the contents to be written to the file
        :param str ftype: the type of the file
        :rtype str
        """
        ret_path = self.generate_tmp_path(ftype)
        wfile = open(ret_path, "w")
        wfile.write(contents)
        wfile.close()
        os.chmod(ret_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
        return ret_path

    def copy_file_to_tmp(self, file_path):
        """
        Copies the file into a new file in the tmp directory and returns the path
        :param str file_path: the path to the file
        :rtype: str
        """
        parts = file_path.split(".")
        ret_path = self.generate_tmp_path(parts[-1])
        import shutil
        shutil.copy(file_path, ret_path)
        os.chmod(ret_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
        return ret_path

