import glob
import os
import pyclbr

import commctrl
import pywin.framework.scriptutils
import regutil
import win32api
import win32con
import win32ui
from pywin.mfc import afxres, dialog

from . import hierlist


class HLIErrorItem(hierlist.HierListItem):
    def __init__(self, text):
        self.text = text
        hierlist.HierListItem.__init__(self)

    def GetText(self):
        return self.text


class HLICLBRItem(hierlist.HierListItem):
    def __init__(self, name: str, file, lineno, suffix=""):
        # If the 'name' object itself has a .name, use it.  Not sure
        # how this happens, but seems pyclbr related.
        # See PyWin32 bug 817035
        self.name = getattr(name, "name", name)
        self.file = file
        self.lineno = lineno
        self.suffix = suffix

    def __lt__(self, other):
        return self.name < other.name

    def __eq__(self, other):
        return self.name == other.name

    def GetText(self):
        return self.name + self.suffix

    def TakeDefaultAction(self):
        if self.file:
            pywin.framework.scriptutils.JumpToDocument(
                self.file, self.lineno, bScrollToTop=1
            )
        else:
            win32ui.SetStatusText("The source of this object is unknown")

    def PerformItemSelected(self):
        if self.file is None:
            msg = f"{self.name} - source can not be located."
        else:
            msg = "%s defined at line %d of %s" % (self.name, self.lineno, self.file)
        win32ui.SetStatusText(msg)


class HLICLBRClass(HLICLBRItem):
    def __init__(self, clbrclass, suffix=""):
        try:
            name = clbrclass.name
            file = clbrclass.file
            lineno = clbrclass.lineno
            self.super = clbrclass.super
            self.methods = clbrclass.methods
        except AttributeError:
            name = clbrclass
            file = lineno = None
            self.super = []
            self.methods = {}
        HLICLBRItem.__init__(self, name, file, lineno, suffix)

    def GetSubList(self):
        ret = []
        for c in self.super:
            ret.append(HLICLBRClass(c, " (Parent class)"))
        for meth, lineno in self.methods.items():
            ret.append(HLICLBRMethod(meth, self.file, lineno, " (method)"))
        return ret

    def IsExpandable(self):
        return len(self.methods) + len(self.super)

    def GetBitmapColumn(self):
        return 21


class HLICLBRFunction(HLICLBRClass):
    def GetBitmapColumn(self):
        return 22


class HLICLBRMethod(HLICLBRItem):
    def GetBitmapColumn(self):
        return 22


class HLIModuleItem(hierlist.HierListItem):
    def __init__(self, path):
        hierlist.HierListItem.__init__(self)
        self.path = path

    def GetText(self):
        return os.path.split(self.path)[1] + " (module)"

    def IsExpandable(self):
        return 1

    def TakeDefaultAction(self):
        win32ui.GetApp().OpenDocumentFile(self.path)

    def GetBitmapColumn(self):
        col = 4  # Default
        try:
            if win32api.GetFileAttributes(self.path) & win32con.FILE_ATTRIBUTE_READONLY:
                col = 5
        except win32api.error:
            pass
        return col

    def GetSubList(self):
        mod, path = pywin.framework.scriptutils.GetPackageModuleName(self.path)
        win32ui.SetStatusText("Building class list - please wait...", 1)
        win32ui.DoWaitCursor(1)
        try:
            try:
                reader = pyclbr.readmodule_ex  # Post 1.5.2 interface.
                extra_msg = " or functions"
            except AttributeError:
                reader = pyclbr.readmodule
                extra_msg = ""
            data = reader(mod, [path])
            if data:
                ret = []
                for item in data.values():
                    if (
                        item.__class__ != pyclbr.Class
                    ):  # ie, it is a pyclbr Function instance (only introduced post 1.5.2)
                        ret.append(HLICLBRFunction(item, " (function)"))
                    else:
                        ret.append(HLICLBRClass(item, " (class)"))
                ret.sort()
                return ret
            else:
                return [HLIErrorItem(f"No Python classes{extra_msg} in module.")]
        finally:
            win32ui.DoWaitCursor(0)
            win32ui.SetStatusText(win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE))


def MakePathSubList(path):
    ret = []
    for filename in glob.glob(os.path.join(path, "*")):
        if os.path.isdir(filename) and os.path.isfile(
            os.path.join(filename, "__init__.py")
        ):
            ret.append(HLIDirectoryItem(filename, os.path.split(filename)[1]))
        else:
            if os.path.splitext(filename)[1].lower() in [".py", ".pyw"]:
                ret.append(HLIModuleItem(filename))
    return ret


class HLIDirectoryItem(hierlist.HierListItem):
    def __init__(self, path, displayName=None, bSubDirs=0):
        hierlist.HierListItem.__init__(self)
        self.path = path
        self.bSubDirs = bSubDirs
        if displayName:
            self.displayName = displayName
        else:
            self.displayName = path

    def IsExpandable(self):
        return 1

    def GetText(self):
        return self.displayName

    def GetSubList(self):
        ret = MakePathSubList(self.path)
        if (
            os.path.split(self.path)[1] == "win32com"
        ):  # Complete and utter hack for win32com.
            try:
                path = win32api.GetFullPathName(
                    os.path.join(self.path, "..\\win32comext")
                )
                ret.extend(MakePathSubList(path))
            except win32ui.error:
                pass
        return ret


class HLIProjectRoot(hierlist.HierListItem):
    def __init__(self, projectName, displayName=None):
        hierlist.HierListItem.__init__(self)
        self.projectName = projectName
        self.displayName = displayName or projectName

    def GetText(self):
        return self.displayName

    def IsExpandable(self):
        return 1

    def GetSubList(self):
        paths = regutil.GetRegisteredNamedPath(self.projectName)
        pathList = paths.split(";")
        if len(pathList) == 1:  # Single dir - don't bother putting the dir in
            ret = MakePathSubList(pathList[0])
        else:
            ret = list(map(HLIDirectoryItem, pathList))
        return ret


class HLIRoot(hierlist.HierListItem):
    def __init__(self):
        hierlist.HierListItem.__init__(self)

    def IsExpandable(self):
        return 1

    def GetSubList(self):
        keyStr = regutil.BuildDefaultPythonKey() + "\\PythonPath"
        hKey = win32api.RegOpenKey(regutil.GetRootKey(), keyStr)
        try:
            ret = []
            ret.append(HLIProjectRoot("", "Standard Python Library"))  # The core path.
            index = 0
            while 1:
                try:
                    ret.append(HLIProjectRoot(win32api.RegEnumKey(hKey, index)))
                    index += 1
                except win32api.error:
                    break
            return ret
        finally:
            win32api.RegCloseKey(hKey)


class dynamic_browser(dialog.Dialog):
    style = win32con.WS_OVERLAPPEDWINDOW | win32con.WS_VISIBLE
    cs = (
        win32con.WS_CHILD
        | win32con.WS_VISIBLE
        | commctrl.TVS_HASLINES
        | commctrl.TVS_LINESATROOT
        | commctrl.TVS_HASBUTTONS
    )

    dt = [
        ["Python Projects", (0, 0, 200, 200), style, None, (8, "MS Sans Serif")],
        ["SysTreeView32", None, win32ui.IDC_LIST1, (0, 0, 200, 200), cs],
    ]

    def __init__(self, hli_root):
        dialog.Dialog.__init__(self, self.dt)
        self.hier_list = hierlist.HierListWithItems(hli_root, win32ui.IDB_BROWSER_HIER)
        self.HookMessage(self.on_size, win32con.WM_SIZE)

    def OnInitDialog(self):
        self.hier_list.HierInit(self)
        return dialog.Dialog.OnInitDialog(self)

    def on_size(self, params):
        lparam = params[3]
        w = win32api.LOWORD(lparam)
        h = win32api.HIWORD(lparam)
        self.GetDlgItem(win32ui.IDC_LIST1).MoveWindow((0, 0, w, h))


def BrowseDialog():
    root = HLIRoot()
    if not root.IsExpandable():
        raise TypeError(
            "Browse() argument must have __dict__ attribute, or be a Browser supported type"
        )

    dlg = dynamic_browser(root)
    dlg.CreateWindow()


def DockableBrowserCreator(parent):
    root = HLIRoot()
    hl = hierlist.HierListWithItems(root, win32ui.IDB_BROWSER_HIER)

    style = (
        win32con.WS_CHILD
        | win32con.WS_VISIBLE
        | win32con.WS_BORDER
        | commctrl.TVS_HASLINES
        | commctrl.TVS_LINESATROOT
        | commctrl.TVS_HASBUTTONS
    )

    control = win32ui.CreateTreeCtrl()
    control.CreateWindow(style, (0, 0, 150, 300), parent, win32ui.IDC_LIST1)
    list = hl.HierInit(parent, control)
    return control


def DockablePathBrowser():
    import pywin.docking.DockingBar

    bar = pywin.docking.DockingBar.DockingBar()
    bar.CreateWindow(
        win32ui.GetMainFrame(), DockableBrowserCreator, "Path Browser", 0x8E0A
    )
    bar.SetBarStyle(
        bar.GetBarStyle()
        | afxres.CBRS_TOOLTIPS
        | afxres.CBRS_FLYBY
        | afxres.CBRS_SIZE_DYNAMIC
    )
    bar.EnableDocking(afxres.CBRS_ALIGN_ANY)
    win32ui.GetMainFrame().DockControlBar(bar)


# The "default" entry point
Browse = DockablePathBrowser
