import os
import sublime
import sublime_plugin
import re
import logging
import errno

SETTINGS = [
    "alias",
    "default_initial",
    "use_cursor_text",
    "show_files",
    "show_path",
    "default_root",
    "default_path",
    "default_folder_index",
    "os_specific_alias",
    "ignore_case",
    "alias_root",
    "alias_path",
    "alias_folder_index",
    "debug",
    "auto_refresh_sidebar",
    "completion_type",
    "complete_single_entry",
    "use_folder_name",
    "relative_from_current",
    "default_extension"
]
VIEW_NAME = "AdvancedNewFileCreation"
WIN_ROOT_REGEX = r"[a-zA-Z]:(/|\\)"
NIX_ROOT_REGEX = r"^/"
HOME_REGEX = r"^~"
PLATFORM = sublime.platform().lower()
IS_ST3 = int(sublime.version()) > 3000

# Set up logger
logging.basicConfig(format='[AdvancedNewFile] %(levelname)s %(message)s')
logger = logging.getLogger()


class AdvancedNewFileCommand(sublime_plugin.WindowCommand):
    def run(self, is_python=False, initial_path=None):
        PLATFORM = sublime.platform().lower()
        self.root = None
        self.alias_root = None
        self.top_level_split_char = ":"
        self.is_python = is_python
        self.view = self.window.active_view()

        # Settings will be based on the view
        self.settings = get_settings(self.view)
        self.aliases = self.get_aliases()
        self.show_path = self.settings.get("show_path")
        self.default_folder_index = self.settings.get("default_folder_index")
        self.alias_folder_index = self.settings.get("alias_folder_index")
        default_root = self.get_default_root(self.settings.get("default_root"))
        if default_root == "path":
            self.root = os.path.expanduser(self.settings.get("default_path"))
            default_root = ""
        self.root, path = self.split_path(default_root)

        # Search for initial string
        if initial_path is not None:
            path = initial_path
        else:
            path = self.settings.get("default_initial", "")
            if self.settings.get("use_cursor_text", False):
                tmp = self.get_cursor_path()
                if tmp != "":
                    path = tmp

        alias_root = self.get_default_root(self.settings.get("alias_root"), True)
        if alias_root == "path":
            self.alias_root = os.path.expanduser(self.settings.get("alias_path"))
            alias_root = ""
        self.alias_root, tmp = self.split_path(alias_root, True)

        debug = self.settings.get("debug") or False
        if debug:
            logger.setLevel(logging.DEBUG)
        else:
            logger.setLevel(logging.ERROR)
        # Get user input
        self.show_filename_input(path)

    def get_aliases(self):
        aliases = self.settings.get("alias")
        all_os_aliases = self.settings.get("os_specific_alias")
        for key in all_os_aliases:
            if PLATFORM in all_os_aliases.get(key):
                aliases[key] = all_os_aliases.get(key).get(PLATFORM)

        return aliases

    def get_default_root(self, string, is_alias=False):
        root = ""

        if string == "home":
            root = "~/"
        elif string == "current":
            root = self.top_level_split_char
        elif string == "project_folder":
            if is_alias:
                folder_index = self.alias_folder_index
            else:
                folder_index = self.default_folder_index
            if len(self.window.folders()) <= folder_index:
                if is_alias:
                    self.alias_folder_index = 0
                else:
                    self.default_folder_index = 0
        elif string == "top_folder":
            if is_alias:
                self.alias_folder_index = 0
            else:
                self.default_folder_index = 0
        elif string == "path":
            root = "path"
        else:
            logger.error("Invalid specifier for \"default_root\"")
        return root

    def split_path(self, path="", is_alias=False):
        HOME_REGEX = r"^~[/\\]"
        root = None
        try:
            # Parse windows root
            if PLATFORM == "windows":
                if re.match(WIN_ROOT_REGEX, path):
                    root = path[0:3]
                    path = path[3:]

            # Parse if alias
            if self.top_level_split_char in path and root == None:
                parts = path.rsplit(self.top_level_split_char, 1)
                root, path = self.translate_alias(parts[0])
                path_list = []
                if path != "":
                    path_list.append(path)
                if parts[1] != "":
                    path_list.append(parts[1])
                path = self.top_level_split_char.join(path_list)
            elif re.match(r"^/", path):
                path_offset = 1
                if PLATFORM == "windows":
                    match = re.match(r"^/([a-zA-Z])/", path)
                    if match:
                        root = "%s:\\" % match.group(1)
                        path_offset = 3
                    else:
                        root, _ = os.path.splitdrive(self.view.file_name())
                        root += "\\"
                else:
                    root = "/"
                path = path[path_offset:]
            # Parse if tilde used
            elif re.match(HOME_REGEX, path) and root == None:
                root = os.path.expanduser("~")
                path = path[2:]
            elif re.match(r"^\.{1,2}[/\\]", path) and self.settings.get("relative_from_current", False):
                path_index = 2
                root = os.path.dirname(self.view.file_name())
                if re.match(r"^\.{2}[/\\]", path):
                    root = os.path.dirname(root)
                    path_index = 3
                path = path[path_index:]

            # Default
            if root == None:
                if is_alias:
                    root = self.alias_root
                    folder_index = self.alias_folder_index
                else:
                    root = self.root
                    folder_index = self.default_folder_index
                root = root or self.window.folders()[folder_index]
        except IndexError:
            root = os.path.expanduser("~")


        return root, path

    def translate_alias(self, path):
        root = None
        split_path = None
        if path == "" and self.view is not None:
            filename = self.view.file_name()
            if filename is not None:
                root = os.path.dirname(filename)
        else:
            split_path = path.split(self.top_level_split_char)
            join_index = len(split_path) - 1
            target = path
            root_found = False
            while join_index >= 0 and not root_found:
                # Folder aliases
                for name, folder in get_project_folder_data(self.settings.get("use_folder_name")):
                    if name == target:
                        root = folder
                        root_found = True
                        break
                # Aliases from settings.
                for alias in self.aliases.keys():
                    if alias == target:
                        alias_path = self.aliases.get(alias)
                        if re.search(HOME_REGEX, alias_path) is None:
                            if PLATFORM == "windows":
                                if re.search(WIN_ROOT_REGEX, alias_path) is None:
                                    root = os.path.join(self.alias_root, alias_path)
                                    break
                            else:
                                if re.search(NIX_ROOT_REGEX, alias_path) is None:
                                    root = os.path.join(self.alias_root, alias_path)
                                    break
                        root = os.path.expanduser(alias_path)
                        root_found = True
                        break
                remove = re.escape(split_path[join_index])
                target = re.sub(r":%s$" % remove, "", target)
                join_index -= 1

        if root is None:
            return None, path
        elif split_path is None:
            return os.path.abspath(root), ""
        else:
            # Add to index so we re
            join_index += 2
            return os.path.abspath(root), self.top_level_split_char.join(split_path[join_index:])

    def show_filename_input(self, initial=''):
        caption = 'Enter a path for a new file'
        if self.is_python:
            caption = '%s (creates __init__.py in new dirs)' % caption
        self.input_panel_view = self.window.show_input_panel(
            caption, initial,
            self.entered_filename, self.update_filename_input, self.clear
        )

        self.input_panel_view.set_name(VIEW_NAME)
        self.input_panel_view.settings().set("auto_complete_commit_on_tab", False)
        self.input_panel_view.settings().set("tab_completion", False)
        self.input_panel_view.settings().set("translate_tabs_to_spaces", False)
        self.input_panel_view.settings().set("anf_panel", True)

    def update_filename_input(self, path_in):
        if self.settings.get("completion_type") == "windows":
            if "prev_text" in dir(self) and self.prev_text != path_in:
                if self.view is not None:
                    self.view.erase_status("AdvancedNewFile2")
        if path_in.endswith("\t"):
            path_in = path_in.replace("\t", "")
            if self.settings.get("completion_type") == "windows":
                path_in = self.windows_completion(path_in)
            elif self.settings.get("completion_type") == "nix":
                path_in = self.nix_completion(path_in)

        base, path = self.split_path(path_in)

        creation_path = self.generate_creation_path(base, path, True)
        if self.show_path:
            if self.view != None:
                self.view.set_status("AdvancedNewFile", "Creating file at %s " % \
                    creation_path)
            else:
                sublime.status_message("Creating file at %s" % creation_path)
        logger.debug("Creation path is '%s'" % creation_path)

    def generate_completion_list(self, path_in, each_list=False):
        alias_list = []
        dir_list = []
        file_list = []
        self.suggestion_entries = []
        if self.top_level_split_char in path_in or re.match(r"^~[/\\]", path_in):
            pass
        else:
            directory, filename = os.path.split(path_in)
            if len(directory) == 0:
                alias_list += self.generate_alias_auto_complete(filename)
                alias_list += self.generate_project_auto_complete(filename)
        base, path = self.split_path(path_in)
        full_path = self.generate_creation_path(base, path)

        directory, filename = os.path.split(full_path)

        if os.path.isdir(directory):
            for d in os.listdir(directory):
                full_path = os.path.join(directory, d)
                if os.path.isdir(full_path):
                    is_file = False
                elif self.settings.get("show_files"):
                    is_file = True
                else:
                    continue

                if self.compare_entries(d, filename):
                    if is_file:
                        file_list.append(d)
                    else:
                        dir_list.append(d)

        completion_list = alias_list + dir_list + file_list

        return sorted(completion_list), alias_list, dir_list, file_list

    def windows_completion(self, path_in):
        pattern = r"(.*[/\\:])(.*)"
        match = re.match(pattern, path_in)
        if "prev_text" in dir(self) and self.prev_text == path_in:
            self.offset = (self.offset + 1) % len(self.completion_list)
        else:
            # Generate new completion list
            self.completion_list, self.alias_list, self.dir_list, self.file_list = self.generate_completion_list(path_in)
            self.offset = 0

            if len(self.completion_list) == 0:
                if match:
                    self.completion_list = [match.group(2)]
                else:
                    self.completion_list = [path_in]
        match = re.match(pattern, path_in)
        if match :
            completion = self.completion_list[self.offset]
            if self.settings.get("complete_single_entry"):
                if len(self.completion_list) == 1:
                    if completion in self.alias_list:
                        completion += ":"
                    elif completion in self.dir_list:
                        completion += "/"
            new_content = re.sub(pattern, r"\1" , path_in)
            new_content += completion
            first_token = False
        else:
            completion = self.completion_list[self.offset]
            if self.settings.get("complete_single_entry"):
                if len(self.completion_list) == 1:
                    if completion in self.alias_list:
                        completion += ":"
                    elif completion in self.dir_list:
                        completion += "/"
            new_content = completion
            first_token = True

        if len(self.completion_list) > 1:
            if first_token:
                if self.view is not None:
                    if self.completion_list[self.offset] in self.alias_list:
                        self.view.set_status("AdvancedNewFile2", "Alias Completion")
                    elif self.completion_list[self.offset] in self.dir_list:
                        self.view.set_status("AdvancedNewFile2", "Directory Completion")
            self.prev_text = new_content
        else:
            self.prev_text = None
        self.input_panel_view.run_command("anf_replace", {"content": new_content})
        return new_content

    def nix_completion(self, path_in):
        pattern = r"(.*[/\\:])(.*)"

        completion_list, alias_list, dir_list, file_list = self.generate_completion_list(path_in)
        new_content = path_in
        if len(completion_list) > 0:
            common = os.path.commonprefix(completion_list)
            match = re.match(pattern, path_in)
            if match :
                new_content = re.sub(pattern, r"\1", path_in)
                new_content += common
            else:
                new_content = common
            if len(completion_list) > 1:
                dir_list = map(lambda s: s + "/", dir_list)
                alias_list = map(lambda s: s + ":", alias_list)
                status_message_list = sorted(list(dir_list) + list(alias_list) + file_list)
                sublime.status_message(", ".join(status_message_list))
            else:
                if completion_list[0] in alias_list:
                    new_content += ":"
                elif completion_list[0] in dir_list:
                    new_content += "/"
        self.input_panel_view.run_command("anf_replace", {"content": new_content})
        return new_content

    def generate_project_auto_complete(self, base):
        folder_data = get_project_folder_data(self.settings.get("use_folder_name"))
        if len(folder_data) > 1:
            folders = [x[0] for x in folder_data]
            return self.generate_auto_complete(base, folders)
        return []

    def generate_alias_auto_complete(self, base):
        return self.generate_auto_complete(base, self.aliases)

    def generate_auto_complete(self, base, iterable_var):
        sugg = []
        for entry in iterable_var:
            if entry in self.suggestion_entries:
                continue
            self.suggestion_entries.append(entry)
            compare_entry = entry
            compare_base = base
            if self.settings.get("ignore_case"):
                compare_entry = compare_entry.lower()
                compare_base = compare_base.lower()

            if self.compare_entries(compare_entry, compare_base):
                sugg.append(entry)

        return sugg

    def compare_entries(self, compare_entry, compare_base):
        if self.settings.get("ignore_case"):
            compare_entry = compare_entry.lower()
            compare_base = compare_base.lower()

        return compare_entry.startswith(compare_base)


    def generate_creation_path(self, base, path, append_extension=False):
        if PLATFORM == "windows":
            if not re.match(WIN_ROOT_REGEX, base):
                return base + self.top_level_split_char + path
        else:
            if not re.match(NIX_ROOT_REGEX, base):
                return base + self.top_level_split_char + path

        tokens = re.split(r"[/\\]", base) + re.split(r"[/\\]", path)
        if tokens[0] == "":
            tokens[0] = "/"
        if PLATFORM == "windows":
            tokens[0] = base[0:3]

        full_path = os.path.abspath(os.path.join(*tokens))
        if re.search(r"[/\\]$", path) or len(path) == 0:
            full_path += os.path.sep
        elif re.search(r"\.", tokens[-1]):
            if re.search(r"\.$", tokens[-1]):
                full_path += "."
        elif append_extension:
            filename = os.path.basename(full_path)
            if not os.path.exists(full_path):
                full_path += self.settings.get("default_extension", "")
        return full_path

    def entered_filename(self, filename):
        # Check if valid root specified for windows.
        if PLATFORM == "windows":
            if re.match(WIN_ROOT_REGEX, filename):
                root = filename[0:3]
                if not os.path.isdir(root):
                    sublime.error_message(root + " is not a valid root.")
                    self.clear()
                    return

        base, path = self.split_path(filename)
        file_path = self.generate_creation_path(base, path, True)
        # Check for invalid alias specified.
        if self.top_level_split_char in filename and \
            not (PLATFORM == "windows" and re.match(WIN_ROOT_REGEX, base)) and \
            not (PLATFORM != "windows" and re.match(NIX_ROOT_REGEX, base)):
            if base == "":
                error_message = "Current file cannot be resolved."
            else:
                error_message = "'" + base + "' is an invalid alias."
            sublime.error_message(error_message)
        else:
            attempt_open = True
            logger.debug("Creating file at %s", file_path)
            if not os.path.exists(file_path):
                try:
                    self.create(file_path)
                except OSError as e:
                    attempt_open = False
                    sublime.error_message("Cannot create '" + file_path + "'. See console for details")
                    logger.error("Exception: %s '%s'" % (e.strerror, e.filename))
            if attempt_open:
                if os.path.isdir(file_path):
                    if not re.search(r"(/|\\)$", file_path):
                        sublime.error_message("Cannot open view for '" + file_path + "'. It is a directory. ")
                else:
                    self.window.open_file(file_path)
        self.clear()
        self.refresh_sidebar()

    def refresh_sidebar(self):
        if self.settings.get("auto_refresh_sidebar"):
            try:
                self.window.run_command("refresh_folder_list")
            except:
                pass

    def clear(self):
        if self.view != None:
            self.view.erase_status("AdvancedNewFile")
            self.view.erase_status("AdvancedNewFile2")

    def create(self, filename):
        base, filename = os.path.split(filename)
        self.create_folder(base)
        if filename != "":
            open(os.path.join(base, filename), "a").close()

    def create_folder(self, path):
        init_list = []
        if self.is_python:
            temp_path = path
            while not os.path.exists(temp_path):
                init_list.append(temp_path)
                temp_path = os.path.dirname(temp_path)
        try:
            if not os.path.exists(path):
                os.makedirs(path)
        except OSError as ex:
            if ex.errno != errno.EEXIST:
                raise

        for entry in init_list:
            open(os.path.join(entry, '__init__.py'), 'a').close()

    def get_cursor_path(self):
        if self.view == None:
            return ""

        view = self.view
        path = ""
        for region in view.sel():
            syntax = view.scope_name(region.begin())
            if region.begin() != region.end():
                path = view.substr(region)
                break
            if re.match(".*string.quoted.double", syntax) or re.match(".*string.quoted.single", syntax):
                path = view.substr(view.extract_scope(region.begin()))
                path = re.sub('^"|\'', '',  re.sub('"|\'$', '', path.strip()))
                break

        return path


class AnfReplaceCommand(sublime_plugin.TextCommand):
    def run(self, edit, content):
        self.view.replace(edit, sublime.Region(0, self.view.size()), content)


class AdvancedNewFileAtCommand(sublime_plugin.WindowCommand):
    def run(self, dirs):
        if len(dirs) != 1:
            return
        path = dirs[0]
        self.window.run_command("advanced_new_file", {"initial_path": path + os.sep})


    def is_visible(self, dirs):
        settings = sublime.load_settings("AdvancedNewFile.sublime-settings")
        return settings.get("show_sidebar_menu", False) and len(dirs) == 1


def get_settings(view):
    settings = sublime.load_settings("AdvancedNewFile.sublime-settings")
    project_settings = {}
    local_settings = {}
    if view != None:
        project_settings = view.settings().get('AdvancedNewFile', {})

    for setting in SETTINGS:
        local_settings[setting] = settings.get(setting)

    for key in project_settings:
        if key in SETTINGS:
            if key == "alias":
                local_settings[key] = dict(local_settings[key].items() + project_settings.get(key).items())
            else:
                local_settings[key] = project_settings[key]
        else:
            logger.error("AdvancedNewFile[Warning]: Invalid key '%s' in project settings.", key)

    return local_settings

def get_project_folder_data(use_folder_name):
    folders = []
    folder_entries = []
    window = sublime.active_window()
    project_folders = window.folders()

    if IS_ST3:
        project_data = window.project_data()

        if project_data is not None:
            if use_folder_name:
                for folder in project_data.get("folders", []):
                    folder_entries.append({})
            else:
                folder_entries = project_data.get("folders", [])
    else:
        for folder in project_folders:
            folder_entries.append({})
    for index in range(len(folder_entries)):
        folder_path = project_folders[index]
        folder_entry = folder_entries[index]
        if "name" in folder_entry:
            folders.append((folder_entry["name"], folder_path))
        else:
            folders.append((os.path.basename(folder_path), folder_path))

    return folders
