# Regedit - a Registry Editor for Python

import commctrl
import regutil
import win32api
import win32con
import win32ui
from pywin.mfc import dialog, docview, window

from . import hierlist


def SafeApply(fn, args, err_desc=""):
    try:
        fn(*args)
        return 1
    except win32api.error as exc:
        msg = "Error " + err_desc + "\r\n\r\n" + exc.strerror
        win32ui.MessageBox(msg)
        return 0


class SplitterFrame(window.MDIChildWnd):
    def __init__(self):
        # call base CreateFrame
        self.images = None
        window.MDIChildWnd.__init__(self)

    def OnCreateClient(self, cp, context):
        splitter = win32ui.CreateSplitter()
        doc = context.doc
        frame_rect = self.GetWindowRect()
        size = ((frame_rect[2] - frame_rect[0]), (frame_rect[3] - frame_rect[1]) // 2)
        sub_size = (size[0] // 3, size[1])
        splitter.CreateStatic(self, 1, 2)
        # CTreeControl view
        self.keysview = RegistryTreeView(doc)
        # CListControl view
        self.valuesview = RegistryValueView(doc)

        splitter.CreatePane(self.keysview, 0, 0, (sub_size))
        splitter.CreatePane(self.valuesview, 0, 1, (0, 0))  # size ignored.
        splitter.SetRowInfo(0, size[1], 0)
        # Setup items in the imagelist

        return 1

    def OnItemDoubleClick(self, info, extra):
        (hwndFrom, idFrom, code) = info
        if idFrom == win32ui.AFX_IDW_PANE_FIRST:
            # Tree control
            return None
        elif idFrom == win32ui.AFX_IDW_PANE_FIRST + 1:
            item = self.keysview.SelectedItem()
            self.valuesview.EditValue(item)
            return 0
            # List control
        else:
            return None  # Pass it on

    def PerformItemSelected(self, item):
        return self.valuesview.UpdateForRegItem(item)

    def OnDestroy(self, msg):
        window.MDIChildWnd.OnDestroy(self, msg)
        if self.images:
            self.images.DeleteImageList()
            self.images = None


class RegistryTreeView(docview.TreeView):
    def OnInitialUpdate(self):
        rc = self._obj_.OnInitialUpdate()
        self.frame = self.GetParent().GetParent()
        self.hierList = hierlist.HierListWithItems(
            self.GetHLIRoot(), win32ui.IDB_HIERFOLDERS, win32ui.AFX_IDW_PANE_FIRST
        )
        self.hierList.HierInit(self.frame, self.GetTreeCtrl())
        self.hierList.SetStyle(
            commctrl.TVS_HASLINES | commctrl.TVS_LINESATROOT | commctrl.TVS_HASBUTTONS
        )
        self.hierList.PerformItemSelected = self.PerformItemSelected

        self.frame.HookNotify(self.frame.OnItemDoubleClick, commctrl.NM_DBLCLK)
        self.frame.HookNotify(self.OnItemRightClick, commctrl.NM_RCLICK)

    # 		self.HookMessage(self.OnItemRightClick, win32con.WM_RBUTTONUP)

    def GetHLIRoot(self):
        doc = self.GetDocument()
        regroot = doc.root
        subkey = doc.subkey
        return HLIRegistryKey(regroot, subkey, "Root")

    def OnItemRightClick(self, notify_data, extra):
        # First select the item we right-clicked on.
        pt = self.ScreenToClient(win32api.GetCursorPos())
        flags, hItem = self.HitTest(pt)
        if hItem == 0 or commctrl.TVHT_ONITEM & flags == 0:
            return None
        self.Select(hItem, commctrl.TVGN_CARET)

        menu = win32ui.CreatePopupMenu()
        menu.AppendMenu(win32con.MF_STRING | win32con.MF_ENABLED, 1000, "Add Key")
        menu.AppendMenu(win32con.MF_STRING | win32con.MF_ENABLED, 1001, "Add Value")
        menu.AppendMenu(win32con.MF_STRING | win32con.MF_ENABLED, 1002, "Delete Key")
        self.HookCommand(self.OnAddKey, 1000)
        self.HookCommand(self.OnAddValue, 1001)
        self.HookCommand(self.OnDeleteKey, 1002)
        menu.TrackPopupMenu(win32api.GetCursorPos())  # track at mouse position.
        return None

    def OnDeleteKey(self, command, code):
        hitem = self.hierList.GetSelectedItem()
        item = self.hierList.ItemFromHandle(hitem)
        msg = f"Are you sure you wish to delete the key '{item.keyName}'?"
        id = win32ui.MessageBox(msg, None, win32con.MB_YESNO)
        if id != win32con.IDYES:
            return
        if SafeApply(
            win32api.RegDeleteKey, (item.keyRoot, item.keyName), "deleting registry key"
        ):
            # Get the items parent.
            try:
                hparent = self.GetParentItem(hitem)
            except win32ui.error:
                hparent = None
            self.hierList.Refresh(hparent)

    def OnAddKey(self, command, code):
        val = dialog.GetSimpleInput("New key name", "", "Add new key")
        if val is None:
            return  # cancelled.
        hitem = self.hierList.GetSelectedItem()
        item = self.hierList.ItemFromHandle(hitem)
        if SafeApply(win32api.RegCreateKey, (item.keyRoot, item.keyName + "\\" + val)):
            self.hierList.Refresh(hitem)

    def OnAddValue(self, command, code):
        val = dialog.GetSimpleInput("New value", "", "Add new value")
        if val is None:
            return  # cancelled.
        hitem = self.hierList.GetSelectedItem()
        item = self.hierList.ItemFromHandle(hitem)
        if SafeApply(
            win32api.RegSetValue, (item.keyRoot, item.keyName, win32con.REG_SZ, val)
        ):
            # Simply re-select the current item to refresh the right spitter.
            self.PerformItemSelected(item)

    # 			self.Select(hitem, commctrl.TVGN_CARET)

    def PerformItemSelected(self, item):
        return self.frame.PerformItemSelected(item)

    def SelectedItem(self):
        return self.hierList.ItemFromHandle(self.hierList.GetSelectedItem())

    def SearchSelectedItem(self):
        handle = self.hierList.GetChildItem(0)
        while 1:
            # print("State is", self.hierList.GetItemState(handle, -1))
            if self.hierList.GetItemState(handle, commctrl.TVIS_SELECTED):
                # print("Item is ", self.hierList.ItemFromHandle(handle))
                return self.hierList.ItemFromHandle(handle)
            handle = self.hierList.GetNextSiblingItem(handle)


class RegistryValueView(docview.ListView):
    def OnInitialUpdate(self):
        hwnd = self._obj_.GetSafeHwnd()
        style = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE)
        win32api.SetWindowLong(
            hwnd,
            win32con.GWL_STYLE,
            (style & ~commctrl.LVS_TYPEMASK) | commctrl.LVS_REPORT,
        )

        itemDetails = (commctrl.LVCFMT_LEFT, 100, "Name", 0)
        self.InsertColumn(0, itemDetails)
        itemDetails = (commctrl.LVCFMT_LEFT, 500, "Data", 0)
        self.InsertColumn(1, itemDetails)

    def UpdateForRegItem(self, item):
        self.DeleteAllItems()
        hkey = win32api.RegOpenKey(item.keyRoot, item.keyName)
        try:
            valNum = 0
            ret = []
            while 1:
                try:
                    res = win32api.RegEnumValue(hkey, valNum)
                except win32api.error:
                    break
                name = res[0]
                if not name:
                    name = "(Default)"
                self.InsertItem(valNum, name)
                self.SetItemText(valNum, 1, str(res[1]))
                valNum += 1
        finally:
            win32api.RegCloseKey(hkey)

    def EditValue(self, item):
        # Edit the current value
        class EditDialog(dialog.Dialog):
            def __init__(self, item):
                self.item = item
                dialog.Dialog.__init__(self, win32ui.IDD_LARGE_EDIT)

            def OnInitDialog(self):
                self.SetWindowText("Enter new value")
                self.GetDlgItem(win32con.IDCANCEL).ShowWindow(win32con.SW_SHOW)
                self.edit = self.GetDlgItem(win32ui.IDC_EDIT1)
                # Modify the edit windows style
                style = win32api.GetWindowLong(
                    self.edit.GetSafeHwnd(), win32con.GWL_STYLE
                )
                style &= ~win32con.ES_WANTRETURN
                win32api.SetWindowLong(
                    self.edit.GetSafeHwnd(), win32con.GWL_STYLE, style
                )
                self.edit.SetWindowText(str(self.item))
                self.edit.SetSel(-1)
                return dialog.Dialog.OnInitDialog(self)

            def OnDestroy(self, msg):
                self.newvalue = self.edit.GetWindowText()

        try:
            index = self.GetNextItem(-1, commctrl.LVNI_SELECTED)
        except win32ui.error:
            return  # No item selected.

        if index == 0:
            keyVal = ""
        else:
            keyVal = self.GetItemText(index, 0)
        # Query for a new value.
        try:
            newVal = self.GetItemsCurrentValue(item, keyVal)
        except TypeError as details:
            win32ui.MessageBox(details)
            return

        d = EditDialog(newVal)
        if d.DoModal() == win32con.IDOK:
            try:
                self.SetItemsCurrentValue(item, keyVal, d.newvalue)
            except win32api.error as exc:
                win32ui.MessageBox("Error setting value\r\n\n%s" % exc.strerror)
            self.UpdateForRegItem(item)

    def GetItemsCurrentValue(self, item, valueName):
        hkey = win32api.RegOpenKey(item.keyRoot, item.keyName)
        try:
            val, type = win32api.RegQueryValueEx(hkey, valueName)
            if type != win32con.REG_SZ:
                raise TypeError("Only strings can be edited")
            return val
        finally:
            win32api.RegCloseKey(hkey)

    def SetItemsCurrentValue(self, item, valueName, value):
        # ** Assumes already checked is a string.
        hkey = win32api.RegOpenKey(
            item.keyRoot, item.keyName, 0, win32con.KEY_SET_VALUE
        )
        try:
            win32api.RegSetValueEx(hkey, valueName, 0, win32con.REG_SZ, value)
        finally:
            win32api.RegCloseKey(hkey)


class RegTemplate(docview.DocTemplate):
    def __init__(self):
        docview.DocTemplate.__init__(
            self, win32ui.IDR_PYTHONTYPE, None, SplitterFrame, None
        )

    # 	def InitialUpdateFrame(self, frame, doc, makeVisible=1):
    # 		self._obj_.InitialUpdateFrame(frame, doc, makeVisible) # call default handler.
    # 		frame.InitialUpdateFrame(doc, makeVisible)

    def OpenRegistryKey(
        self, root=None, subkey=None
    ):  # Use this instead of OpenDocumentFile.
        # Look for existing open document
        if root is None:
            root = regutil.GetRootKey()
        if subkey is None:
            subkey = regutil.BuildDefaultPythonKey()
        for doc in self.GetDocumentList():
            if doc.root == root and doc.subkey == subkey:
                doc.GetFirstView().ActivateFrame()
                return doc
        # not found - new one.
        doc = RegDocument(self, root, subkey)
        frame = self.CreateNewFrame(doc)
        doc.OnNewDocument()
        self.InitialUpdateFrame(frame, doc, 1)
        return doc


class RegDocument(docview.Document):
    def __init__(self, template, root, subkey):
        docview.Document.__init__(self, template)
        self.root = root
        self.subkey = subkey
        self.SetTitle("Registry Editor: " + subkey)

    def OnOpenDocument(self, name):
        raise TypeError("This template can not open files")
        return 0


class HLIRegistryKey(hierlist.HierListItem):
    def __init__(self, keyRoot, keyName, userName):
        self.keyRoot = keyRoot
        self.keyName = keyName
        self.userName = userName
        hierlist.HierListItem.__init__(self)

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

    def __eq__(self, other):
        return (
            self.keyRoot == other.keyRoot
            and self.keyName == other.keyName
            and self.userName == other.userName
        )

    def __repr__(self):
        return "<{} with root={}, key={}>".format(
            self.__class__.__name__,
            self.keyRoot,
            self.keyName,
        )

    def GetText(self):
        return self.userName

    def IsExpandable(self):
        # All keys are expandable, even if they currently have zero children.
        return 1

    ##		hkey = win32api.RegOpenKey(self.keyRoot, self.keyName)
    ##		try:
    ##			keys, vals, dt = win32api.RegQueryInfoKey(hkey)
    ##			return (keys>0)
    ##		finally:
    ##			win32api.RegCloseKey(hkey)

    def GetSubList(self):
        hkey = win32api.RegOpenKey(self.keyRoot, self.keyName)
        win32ui.DoWaitCursor(1)
        try:
            keyNum = 0
            ret = []
            while 1:
                try:
                    key = win32api.RegEnumKey(hkey, keyNum)
                except win32api.error:
                    break
                ret.append(HLIRegistryKey(self.keyRoot, self.keyName + "\\" + key, key))
                keyNum += 1
        finally:
            win32api.RegCloseKey(hkey)
            win32ui.DoWaitCursor(0)
        return ret


template = RegTemplate()


def EditRegistry(root=None, key=None):
    doc = template.OpenRegistryKey(root, key)


if __name__ == "__main__":
    EditRegistry()
