// SystemPage.cpp

#include "StdAfx.h"

#include "../../../Common/MyWindows.h"

#if defined(__MINGW32__) || defined(__MINGW64__)
#include <shlobj.h>
#else
#include <ShlObj.h>
#endif

#include "../../../Common/Defs.h"
#include "../../../Common/StringConvert.h"

#include "../../../Windows/DLL.h"
#include "../../../Windows/ErrorMsg.h"

#include "HelpUtils.h"
#include "IFolder.h"
#include "LangUtils.h"
#include "PropertyNameRes.h"
#include "SystemPage.h"
#include "SystemPageRes.h"

using namespace NWindows;

#ifndef _UNICODE
extern bool g_IsNT;
#endif

#ifdef Z7_LANG
static const UInt32 kLangIDs[] =
{
  IDT_SYSTEM_ASSOCIATE
};
#endif

#define kSystemTopic "FM/options.htm#system"

CSysString CModifiedExtInfo::GetString() const
{
  const char *s;
  if (State == kExtState_7Zip)
    s = "7-Zip ZS";
  else if (State == kExtState_Clear)
    s = "";
  else if (Other7Zip)
    s = "[7-Zip ZS]";
  else
    return ProgramKey;
  return CSysString (s);
}


int CSystemPage::AddIcon(const UString &iconPath, int iconIndex)
{
  if (iconPath.IsEmpty())
    return -1;
  if (iconIndex == -1)
    iconIndex = 0;
  
  HICON hicon;
  
  #ifdef UNDER_CE
  ExtractIconExW(iconPath, iconIndex, NULL, &hicon, 1);
  if (!hicon)
  #else
  // we expand path from REG_EXPAND_SZ registry item.
  UString path;
  const DWORD size = MAX_PATH + 10;
  const DWORD needLen = ::ExpandEnvironmentStringsW(iconPath, path.GetBuf(size + 2), size);
  path.ReleaseBuf_CalcLen(size);
  if (needLen == 0 || needLen >= size)
    path = iconPath;
  const UINT num = ExtractIconExW(path, iconIndex, NULL, &hicon, 1);
  if (num != 1 || !hicon)
  #endif
    return -1;
  
  _imageList.AddIcon(hicon);
  DestroyIcon(hicon);
  return (int)(_numIcons++);
}


void CSystemPage::RefreshListItem(unsigned group, unsigned listIndex)
{
  const CAssoc &assoc = _items[GetRealIndex(listIndex)];
  _listView.SetSubItem(listIndex, group + 1, assoc.Pair[group].GetString());
  LVITEMW newItem;
  memset(&newItem, 0, sizeof(newItem));
  newItem.iItem = (int)listIndex;
  newItem.mask = LVIF_IMAGE;
  newItem.iImage = assoc.GetIconIndex();
  _listView.SetItem(&newItem);
}


void CSystemPage::ChangeState(unsigned group, const CUIntVector &indices)
{
  if (indices.IsEmpty())
    return;

  bool thereAreClearItems = false;
  unsigned counters[3] = { 0, 0, 0 };
  
  unsigned i;
  for (i = 0; i < indices.Size(); i++)
  {
    const CModifiedExtInfo &mi = _items[GetRealIndex(indices[i])].Pair[group];
    int state = kExtState_7Zip;
    if (mi.State == kExtState_7Zip)
      state = kExtState_Clear;
    else if (mi.State == kExtState_Clear)
    {
      thereAreClearItems = true;
      if (mi.Other)
        state = kExtState_Other;
    }
    counters[state]++;
  }

  int state = kExtState_Clear;
  if (counters[kExtState_Other] != 0)
    state = kExtState_Other;
  else if (counters[kExtState_7Zip] != 0)
    state = kExtState_7Zip;
  
  for (i = 0; i < indices.Size(); i++)
  {
    unsigned listIndex = indices[i];
    CAssoc &assoc = _items[GetRealIndex(listIndex)];
    CModifiedExtInfo &mi = assoc.Pair[group];
    bool change = false;
    
    switch (state)
    {
      case kExtState_Clear: change = true; break;
      case kExtState_Other: change = mi.Other; break;
      default: change = !(mi.Other && thereAreClearItems); break;
    }
    
    if (change)
    {
      mi.State = state;
      RefreshListItem(group, listIndex);
    }
  }
  
  _needSave = true;
  Changed();
}


bool CSystemPage::OnInit()
{
  _needSave = false;

#ifdef Z7_LANG
  LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs));
#endif

  _listView.Attach(GetItem(IDL_SYSTEM_ASSOCIATE));
  _listView.SetUnicodeFormat();
  DWORD newFlags = LVS_EX_FULLROWSELECT;
  _listView.SetExtendedListViewStyle(newFlags, newFlags);

  _numIcons = 0;
  _imageList.Create(16, 16, ILC_MASK | ILC_COLOR32, 0, 0);

  _listView.SetImageList(_imageList, LVSIL_SMALL);

  _listView.InsertColumn(0, LangString(IDS_PROP_FILE_TYPE), 80);

  UString s;

  #if NUM_EXT_GROUPS == 1
    s = "Program";
  #else
    #ifndef UNDER_CE
      const unsigned kSize = 256;
      BOOL res;

      DWORD size = kSize;

      #ifndef _UNICODE
      if (!g_IsNT)
      {
        AString s2;
        res = GetUserNameA(s2.GetBuf(size), &size);
        s2.ReleaseBuf_CalcLen(MyMin((unsigned)size, kSize));
        s = GetUnicodeString(s2);
      }
      else
      #endif
      {
        res = GetUserNameW(s.GetBuf(size), &size);
        s.ReleaseBuf_CalcLen(MyMin((unsigned)size, kSize));
      }
    
      if (!res)
    #endif
        s = "Current User";
  #endif

  LV_COLUMNW ci;
  ci.mask = LVCF_TEXT | LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM;
  ci.cx = 152;
  ci.fmt = LVCFMT_CENTER;
  ci.pszText = s.Ptr_non_const();
  ci.iSubItem = 1;
  _listView.InsertColumn(1, &ci);

  #if NUM_EXT_GROUPS > 1
  {
    LangString(IDS_SYSTEM_ALL_USERS, s);
    ci.pszText = s.Ptr_non_const();
    ci.iSubItem = 2;
    _listView.InsertColumn(2, &ci);
  }
  #endif

  _extDB.Read();
  _items.Clear();

  FOR_VECTOR (i, _extDB.Exts)
  {
    const CExtPlugins &extInfo = _extDB.Exts[i];

    LVITEMW item;
    item.iItem = (int)i;
    item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
    item.lParam = (LPARAM)i;
    item.iSubItem = 0;
    // ListView always uses internal iImage that is 0 by default?
    // so we always use LVIF_IMAGE.
    item.iImage = -1;
    item.pszText = extInfo.Ext.Ptr_non_const();

    CAssoc assoc;
    const CPluginToIcon &plug = extInfo.Plugins[0];
    assoc.SevenZipImageIndex = AddIcon(plug.IconPath, plug.IconIndex);

    CSysString texts[NUM_EXT_GROUPS];
    unsigned g;
    for (g = 0; g < NUM_EXT_GROUPS; g++)
    {
      CModifiedExtInfo &mi = assoc.Pair[g];
      mi.ReadFromRegistry(GetHKey(g), GetSystemString(extInfo.Ext));
      mi.SetState(plug.IconPath);
      mi.ImageIndex = AddIcon(mi.IconPath, mi.IconIndex);
      texts[g] = mi.GetString();
    }
    item.iImage = assoc.GetIconIndex();
    const int itemIndex = _listView.InsertItem(&item);
    for (g = 0; g < NUM_EXT_GROUPS; g++)
      _listView.SetSubItem((unsigned)itemIndex, 1 + g, texts[g]);
    _items.Add(assoc);
  }
  
  if (_listView.GetItemCount() > 0)
    _listView.SetItemState(0, LVIS_FOCUSED, LVIS_FOCUSED);

  return CPropertyPage::OnInit();
}


static UString GetProgramCommand()
{
  UString s ('\"');
  s += fs2us(NDLL::GetModuleDirPrefix());
  s += "7zFM.exe\" \"%1\"";
  return s;
}


LONG CSystemPage::OnApply()
{
  if (!_needSave)
    return PSNRET_NOERROR;

  const UString command = GetProgramCommand();
  
  LONG res = 0;

  FOR_VECTOR (listIndex, _extDB.Exts)
  {
    unsigned realIndex = GetRealIndex(listIndex);
    const CExtPlugins &extInfo = _extDB.Exts[realIndex];
    CAssoc &assoc = _items[realIndex];

    for (unsigned g = 0; g < NUM_EXT_GROUPS; g++)
    {
      CModifiedExtInfo &mi = assoc.Pair[g];
      HKEY key = GetHKey(g);
      
      if (mi.OldState != mi.State)
      {
        LONG res2 = 0;
        
        if (mi.State == kExtState_7Zip)
        {
          UString title = extInfo.Ext;
          title += " Archive";
          const CPluginToIcon &plug = extInfo.Plugins[0];
          res2 = NRegistryAssoc::AddShellExtensionInfo(key, GetSystemString(extInfo.Ext),
              title, command, plug.IconPath, plug.IconIndex);
        }
        else if (mi.State == kExtState_Clear)
          res2 = NRegistryAssoc::DeleteShellExtensionInfo(key, GetSystemString(extInfo.Ext));
        
        if (res == 0)
          res = res2;
        if (res2 == 0)
          mi.OldState = mi.State;
        
        mi.State = mi.OldState;
        RefreshListItem(g, listIndex);
      }
    }
  }
  
  #ifndef UNDER_CE
  SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
  #endif

  WasChanged = true;

  _needSave = false;
  
  if (res != 0)
    MessageBoxW(*this, NError::MyFormatMessage(res), L"7-Zip ZS", MB_ICONERROR);
  
  return PSNRET_NOERROR;
}


void CSystemPage::OnNotifyHelp()
{
  ShowHelpWindow(kSystemTopic);
}


bool CSystemPage::OnButtonClicked(unsigned buttonID, HWND buttonHWND)
{
  switch (buttonID)
  {
    /*
    case IDC_SYSTEM_SELECT_ALL:
      _listView.SelectAll();
      return true;
    */
    case IDB_SYSTEM_CURRENT:
    case IDB_SYSTEM_ALL:
      ChangeState(buttonID == IDB_SYSTEM_CURRENT ? 0 : 1);
      return true;
  }
  return CPropertyPage::OnButtonClicked(buttonID, buttonHWND);
}


bool CSystemPage::OnNotify(UINT controlID, LPNMHDR lParam)
{
  if (lParam->hwndFrom == HWND(_listView))
  {
    switch (lParam->code)
    {
      case NM_RETURN:
      {
        ChangeState(0);
        return true;
      }

      case NM_CLICK:
      {
        #ifdef UNDER_CE
        NMLISTVIEW *item = (NMLISTVIEW *)lParam;
        #else
        NMITEMACTIVATE *item = (NMITEMACTIVATE *)lParam;
        if (item->uKeyFlags == 0)
        #endif
        {
          if (item->iItem >= 0)
          {
            // unsigned realIndex = GetRealIndex(item->iItem);
            if (item->iSubItem >= 1 && item->iSubItem <= 2)
            {
              CUIntVector indices;
              indices.Add((unsigned)item->iItem);
              ChangeState(item->iSubItem < 2 ? 0 : 1, indices);
            }
          }
        }
        break;
      }
      
      case LVN_KEYDOWN:
      {
        if (OnListKeyDown(LPNMLVKEYDOWN(lParam)))
          return true;
        break;
      }
      
      /*
      case NM_RCLICK:
      case NM_DBLCLK:
      case LVN_BEGINRDRAG:
        // PostMessage(kRefreshpluginsListMessage, 0);
        PostMessage(kUpdateDatabase, 0);
        break;
      */
    }
  }
  return CPropertyPage::OnNotify(controlID, lParam);
}


void CSystemPage::ChangeState(unsigned group)
{
  CUIntVector indices;
  
  int itemIndex = -1;
  while ((itemIndex = _listView.GetNextSelectedItem(itemIndex)) != -1)
    indices.Add((unsigned)itemIndex);
  
  if (indices.IsEmpty())
    FOR_VECTOR (i, _items)
      indices.Add(i);
  
  ChangeState(group, indices);
}


bool CSystemPage::OnListKeyDown(LPNMLVKEYDOWN keyDownInfo)
{
  bool ctrl = IsKeyDown(VK_CONTROL);
  bool alt = IsKeyDown(VK_MENU);

  if (alt)
    return false;

  if ((ctrl && keyDownInfo->wVKey == 'A')
      || (!ctrl && keyDownInfo->wVKey == VK_MULTIPLY))
  {
    _listView.SelectAll();
    return true;
  }

  switch (keyDownInfo->wVKey)
  {
    case VK_SPACE:
    case VK_ADD:
    case VK_SUBTRACT:
    case VK_SEPARATOR:
    case VK_DIVIDE:

    #ifndef UNDER_CE
    case VK_OEM_PLUS:
    case VK_OEM_MINUS:
    #endif

      if (!ctrl)
      {
        ChangeState(keyDownInfo->wVKey == VK_SPACE ? 0 : 1);
        return true;
      }
      break;
  }

  return false;
}
