"""Работа с компонентами Windows"""
import json
import os
import subprocess
import win32com.client
import winreg
from .core import ms
from .path import PATH_TYPES, path2str
from typing import *
shell: dict[str, win32com.client.CDispatch] = {}
shell["WScript"] = win32com.client.Dispatch("WScript.Shell")
_names = {}
_names["lnk"] = {"args": "Arguments", "cwd": "WorkingDirectory", "desc": "Description", "hotkey": "Hotkey", "icon": "IconLocation", "lnk": "FullName", "target": "TargetPath"}


def read_lnk(path: PATH_TYPES) -> dict:
  """Прочитать ярлык .lnk"""
  r = {}
  lnk = shell["WScript"].CreateShortcut(path2str(path, True).replace("/", "\\"))
  r["src"] = lnk
  for k, v in _names["lnk"].items():
    r[k] = getattr(lnk, v, None)
  return r


def write_lnk(path: PATH_TYPES, target: str, args: str = None, cwd: str = None, desc: str = None, hotkey: str = None, icon: str = None):
  """Создать ярлык .lnk"""
  lnk = shell["WScript"].CreateShortCut(path2str(path, True).replace("/", "\\"))
  lnk.TargetPath = target.replace("/", "\\")
  if args != None:
    lnk.Arguments = args
  if cwd != None:
    lnk.WorkingDirectory = cwd.replace("/", "\\")
  if desc != None:
    lnk.Description = desc
  if hotkey != None:
    lnk.Hotkey = hotkey
  if icon != None:
    lnk.IconLocation = icon.replace("/", "\\")
  lnk.Save()
  return lnk


def hide_file(path: PATH_TYPES, *, recursive: bool = False, unhide: bool = False):
  """Скрыть файл используя системную команду `attrib`"""
  args = ["attrib"]
  args.append("-H" if unhide else "+H")
  args.append(path2str(path, True).replace("/", "\\"))
  if recursive:
    args += ["/S", "/D"]
  subprocess.run(args, check=True)


def unhide_file(path: PATH_TYPES, *, recursive: bool = False, hide: bool = False):
  """Противоположность функции скрытия файла"""
  return hide_file(path, recursive=recursive, unhide=not hide)
def path2win(path:PATH_TYPES,**kw)->str:
  kw.setdefault("to_abs",True)
  return path2str(path,**kw).replace("/","\\")

class LnkFile:
  """Открыть/создать файл `.lnk`"""

  def __init__(self, path: PATH_TYPES, readonly: bool = True):
    path = path2win(path)
    if readonly:
      if not os.path.isfile(path):
        raise ms.file.NotAFileError(path)
    self.obj: win32com.client.CDispatch = shell["WScript"].CreateShortcut(path)
    self.readonly = readonly

  def __getitem__(self, k):
    result = getattr(self.obj, k, None)
    if result == "":
      return None
    return result

  def __setitem__(self, k, v):
    if self.readonly:
      raise RuntimeError("Read-only shortcut")
    if v is None:
      v = ""
    setattr(self.obj, k, v)

  def save(self):
    """Сохранить изменения"""
    if self.readonly:
      raise RuntimeError("Read-only shortcut")
    self.obj.Save()

  @property
  def args_str(self) -> None | str:
    return self["Arguments"]

  @args_str.setter
  def args_str(self, v):
    self["Arguments"] = v

  @property
  def args(self) -> None | list[str]:
    line = self.args_str
    if line is None:
      return
    import shlex
    return shlex.split(line, posix=False)

  @args.setter
  def args(self, v):
    if v is None:
      self["Arguments"] = None
      return
    import shlex
    self["Arguments"] = shlex.join(v)

  @property
  def description(self) -> None | str:
    return self["Description"]

  @description.setter
  def description(self, v):
    self["Description"] = v

  @property
  def hot_key(self) -> None | str:
    return self["Hotkey"]

  @hot_key.setter
  def hot_key(self, v):
    self["Hotkey"] = v

  @property
  def icon_loc(self) -> None | str:
    return self["IconLocation"]

  @icon_loc.setter
  def icon_loc(self, v):
    self["IconLocation"] = v

  @property
  def path(self) -> str:
    return self["FullName"]

  @property
  def target_path(self) -> None | str:
    return self["TargetPath"]

  @target_path.setter
  def target_path(self, v):
    self["TargetPath"] = path2win(v)

  @property
  def window_style(self) -> int:
    return self["WindowStyle"]

  @window_style.setter
  def window_style(self, v):
    self["WindowStyle"] =v

  @property
  def working_dir(self) -> None | str:
    return self["WorkingDirectory"]

  @working_dir.setter
  def working_dir(self, v):
    self["WorkingDirectory"] =path2win(v)

# XXX

# Названия и номера локаций
REG_LOC_BY_NAME={
  "~":winreg.HKEY_CURRENT_USER,
  "CLASSES_ROOT":winreg.HKEY_CLASSES_ROOT,
  "CURRENT_CONFIG":winreg.HKEY_CURRENT_CONFIG,
  "CURRENT_USER":winreg.HKEY_CURRENT_USER,
  "DYN_DATA":winreg.HKEY_DYN_DATA,
  "HKCC":winreg.HKEY_CURRENT_CONFIG,
  "HKCR":winreg.HKEY_CLASSES_ROOT,
  "HKCU":winreg.HKEY_CURRENT_USER,
  "HKDD":winreg.HKEY_DYN_DATA,
  "HKLM":winreg.HKEY_LOCAL_MACHINE,
  "HKPD":winreg.HKEY_PERFORMANCE_DATA,
  "HKU":winreg.HKEY_USERS,
  "LOCAL_MACHINE":winreg.HKEY_LOCAL_MACHINE,
  "PERFORMANCE_DATA":winreg.HKEY_PERFORMANCE_DATA,
  "USERS":winreg.HKEY_USERS,
}
REG_LOC_BY_NUM={}
# Названия окружений
ENV_SYSTEM="system"
ENV_SYSTEM64="system64"
ENV_USER="user"
# Добавить локации с исходными названиями
for k in dir(winreg):
  if k.startswith("HKEY_"):
    v=getattr(winreg,k)
    REG_LOC_BY_NAME[k]=v
    REG_LOC_BY_NUM[v]=k
# Часто используемые локации
class RegFastLocations:
  SYSTEM_WINDOWS="/HKLM/SOFTWARE/Microsoft/Windows/CurrentVersion"
  SYSTEM64_WINDOWS="/HKLM/SOFTWARE/WOW6432Node/Microsoft/Windows/CurrentVersion"
  USER_WINDOWS="/HKCU/SOFTWARE/Microsoft/Windows/CurrentVersion"
  #
  SYSTEM_AUTORUN_ONCE=SYSTEM_WINDOWS+"/RunOnce"
  SYSTEM_AUTORUN=SYSTEM_WINDOWS+"/Run"
  SYSTEM_ENV="HKLM/SYSTEM/CurrentControlSet/Control/Session Manager/Environment"
  SYSTEM_PROG_LIST=SYSTEM_WINDOWS+"/Uninstall"
  SYSTEM64_AUTORUN_ONCE=SYSTEM64_WINDOWS+"/RunOnce"
  SYSTEM64_AUTORUN=SYSTEM64_WINDOWS+"/Run"
  SYSTEM64_PROG_LIST=SYSTEM64_WINDOWS+"/Uninstall"
  USER_AUTORUN_ONCE=USER_WINDOWS+"/RunOnce"
  USER_AUTORUN=USER_WINDOWS+"/Run"
  USER_ENV="HKCU/Environment"
  USER_PROG_LIST=USER_WINDOWS+"/Uninstall"
# Функции для работы с путями
def reg_prep_path(path:str,lstrip=False)->str:
  if not path:
    return "" if lstrip else "\\"
  path=path.replace("/","\\").rstrip("\\")
  if not path:
    return "" if lstrip else "\\"
  if lstrip:
    return path.lstrip("\\")
  return path
def reg_path2loc(path:str)->tuple[int,str]:
  path=reg_prep_path(path)
  if path.startswith("~"):
    path="\\"+path
  assert path.startswith("\\")
  parts=path.split("\\",2)
  return REG_LOC_BY_NAME[parts[1].upper()],("\\" if len(parts)==2 else parts[2])
def reg_loc2path(loc:int|str,loc_path:str=None)->str:
  if isinstance(loc,int):
    loc=REG_LOC_BY_NUM[loc]
  return ("\\"+loc.upper()+"\\"+reg_prep_path(loc_path,True)).rstrip("\\")
# Различные варианты для работы с значениями реестра
class RegFolder:
  """Папка реестра. С обязательным указанием типов значений"""
  def __init__(self,loc:int|str,loc_path:str,**kw):
    if isinstance(loc,str):
      loc=REG_LOC_BY_NAME[loc.upper()]
    assert loc in REG_LOC_BY_NUM
    self._loc_num=loc
    self._loc_path=loc_path
    self._opened_key=None
    self._path=None
    self._use_count=0
    self.open_kw=kw
  def __enter__(self):
    if not self._opened_key:
      self._opened_key=winreg.OpenKeyEx(self.loc_num,self.loc_path+"\\",**self.open_kw)
    self._use_count+=1
    return self
  def __exit__(self,*a):
    self._use_count-=1
    if self._opened_key: # Если ключ открыт
      if self._use_count<1: # и никем не используется
        self._opened_key.Close() # он будет закрыт
  def __contains__(self,k)->bool:
    with self:
      try:
        winreg.QueryValueEx(self._opened_key,k)
      except FileNotFoundError:
        return False
    return True
  def __delitem__(self,k):
    with self:
      winreg.DeleteValue(self._opened_key,k)
  def __getitem__(self,k):
    """`value,type`"""
    with self:
      try:
        return winreg.QueryValueEx(self._opened_key,k)
      except FileNotFoundError:
        raise KeyError(k)
  def __setitem__(self,k,v):
    if not isinstance(v,(list,tuple)):
      raise ValueError("Value must be a tuple with a value and a type")
    if not len(v)==2:
      raise ValueError("Value must be a tuple with a value and a type")
    if not isinstance(v[1],int):
      raise ValueError("Second value must be the registry value type")
    value,type=v
    with self:
      winreg.SetValueEx(self._opened_key,k,0,type,value)
  @property
  def auto(self):
    """Тип значений определяется автоматически"""
    return RegFolderAuto(self.loc_num,self.loc_path,**self.open_kw)
  @property
  def json(self):
    """Все значения в реестре хранятся в виде JSON"""
    return RegFolderJson(self.loc_num,self.loc_path,**self.open_kw)
  @property
  def loc_name(self)->str:
    """Название локации в реестре (пример: `HKEY_CURRENT_USER`)"""
    return REG_LOC_BY_NUM[self._loc_num]
  @property
  def loc_num(self)->int:
    """Номер локации в реестре"""
    return self._loc_num
  @property
  def loc_path(self)->str:
    """Путь без названия локации (пример: `SOFTWARE\\Microsoft\\Windows`)"""
    return self._loc_path
  @property
  def loc_tuple(self)->tuple[int,str]:
    """Номер локации и путь раздельно (`loc_num` и `loc_path`)"""
    return self._loc_num,self._loc_path
  @property
  def normal(self):
    return self
  @property
  def path(self)->str:
    """Путь с названием локации (пример: `\\HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows`)"""
    if self._path is None:
      self._path=reg_loc2path(self._loc_num,self._loc_path)
    return self._path
  @classmethod
  def create(cls,loc,loc_path,**kw):
    self=cls(loc,loc_path,**kw)
    self._opened_key=winreg.CreateKeyEx(self.loc_num,self.loc_path+"\\",**kw)
    return self
  @classmethod
  def create_from_path(cls,path:str,**kw):
    return cls.create(*reg_path2loc(path),**kw)
  @classmethod
  def from_path(cls,path:str,**kw):
    return cls(*reg_path2loc(path),**kw)
  def get(self,key:str,default=None):
    with self:
      try:
        return winreg.QueryValueEx(self._opened_key,k)
      except FileNotFoundError:
        return default
  def setdefault(self,key,value,type:int=None):
    if not key in self:
      self[key]=value if type is None else (value,type)
  def pop(self,key:str,default=Ellipsis):
    try:
      value=winreg.QueryValueEx(self._opened_key,k)
      del self[key]
    except FileNotFoundError:
      if default is Ellipsis:
        raise KeyError(key)
      value=default
    return value
class RegFolderAuto(RegFolder):
  """Папка реестра. Тип значений определяется автоматически"""
  def __getitem__(self,k):
    with self:
      try:
        return winreg.QueryValueEx(self._opened_key,k)[0]
      except FileNotFoundError:
        raise KeyError(k)
  def __setitem__(self,k,v):
    if isinstance(v,float):
      int_v=int(v)
      if v==int_v:
        v=int_v
    with self:
      winreg.SetValueEx(self._opened_key,k,0,self._detect_type(v),v)
  def _detect_type(self,v):
    if v is None:
      return winreg.REG_NONE
    if isinstance(v,bytes):
      return winreg.REG_BINARY
    if isinstance(v,float):
      raise ValueError("float cannot be written to the registry")
    if isinstance(v,int):
      if v<0:
        raise ValueError("Number must be positive")
      if v>0xffffffff:
        if v>0xffffffffffffffff:
          raise ValueError("Number is too big")
        return winreg.REG_QWORD
      return winreg.REG_DWORD
    if isinstance(v,str):
      return winreg.REG_MULTI_SZ if "\n" in v else winreg.REG_SZ
    raise ValueError("Failed to detect type")
  @property
  def auto(self):
    return self
  @property
  def normal(self):
    return RegFolder(self.loc_num,self.loc_path,**self.open_kw)
  def setdefault(self,key:str,value):
    if not key in self:
      self[key]=value
class RegFolderJson(RegFolderAuto):
  """Папка реестра. Все значения в реестре хранятся в виде JSON"""
  def __getitem__(self,k):
    return json.loads(RegFolderAuto.__getitem__(self,k))
  def __setitem__(self,k,v,**kw):
    kw.setdefault("separators",(",",":"))
    RegFolderAuto.__setitem__(self,k,json.dumps(v,**kw))
  @property
  def json(self):
    return self
# Проводник реестра (с поддержкой рабочей папки)
class RegExplorer:
  def __init__(self,cwd:str=None):
    self.cwd=cwd
    self.folder_cls:type[RegFolder|RegFolderAuto|RegFolderJson]=RegFolder
    self._opened_keys={}
  def _resolve(self,path) -> tuple[int, str]:
    path=reg_prep_path(path) if path else ""
    if not path.startswith("\\"):
      path=self.cwd+"\\"+path
    return reg_path2loc(path)
  @property
  def cwd(self)->str:
    """Current Working Dir"""
    return self._cwd
  @cwd.setter
  def cwd(self,v):
    if v is None:
      self._cwd="\\"
    else:
      v=reg_prep_path(v) if v else ""
      assert v.startswith("\\")
      self._cwd=v
  def chdir(self,path):
    self.cwd=self._resolve(path)
  def create_folder(self,path,**kw):
    if isinstance(path,RegFolder):
      return path
    return self.folder_cls.create(*self._resolve(path),**kw)
  def open_folder(self,path,**kw):
    if isinstance(path,RegFolder):
      return path
    return self.folder_cls(*self._resolve(path),**kw)
  def get_value(self,path,key,**kw):
    return self.open_folder(path,**kw)[key]
  def set_value(self,path,key,value,type=None,*,mkdir=False,**kw):
    if not type is None:
      value=value,type
    (self.create_folder(path,**kw) if mkdir else self.open_folder(path,**kw))[key]=value
  def del_value(self,path,key,**kw):
    del self.open_folder(path,**kw)[key]
# Утилиты
class utils:
  @staticmethod
  def add_autorun(env:str,name:str,cmd:str,*,expand=True,once=False):
    """Зарегистрировать программу для автозапуска"""
    path=getattr(RegFastLocations,env.upper()+"AUTORUN")
    if once:
      path+="Once"
    RegFolder.create_from_path(path)[name]=cmd,(winreg.REG_EXPAND_SZ if expand else winreg.REG_SZ)
  @staticmethod
  def add_system_autorun(name:str,cmd:str,**kw):
    utils.add_autorun(ENV_SYSTEM,name,cmd,**kw)
  @staticmethod
  def add_system64_autorun(name:str,cmd:str,**kw):
    utils.add_autorun(ENV_SYSTEM64,name,cmd,**kw)
  @staticmethod
  def add_user_autorun(name:str,cmd:str,**kw):
    utils.add_autorun(ENV_USER,name,cmd,**kw)
