// PanelListNotify.cpp

#include "StdAfx.h"

#include "resource.h"

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

#include "../../../Windows/PropVariant.h"
#include "../../../Windows/PropVariantConv.h"

#include "../Common/PropIDUtils.h"
#include "../../PropID.h"

#include "App.h"
#include "Panel.h"
#include "FormatUtils.h"

using namespace NWindows;

/* Unicode characters for space:
0x009C STRING TERMINATOR
0x00B7 Middle dot
0x237D Shouldered open box
0x2420 Symbol for space
0x2422 Blank symbol
0x2423 Open box
*/

#define SPACE_REPLACE_CHAR (wchar_t)(0x2423)
#define SPACE_TERMINATOR_CHAR (wchar_t)(0x9C)

#define INT_TO_STR_SPEC(v) \
  while (v >= 10) { temp[i++] = (Byte)('0' + (unsigned)(v % 10)); v /= 10; } \
  *s++ = (Byte)('0' + (unsigned)v);

static void ConvertSizeToString(UInt64 val, wchar_t *s) throw()
{
  Byte temp[32];
  unsigned i = 0;
  
  if (val <= (UInt32)0xFFFFFFFF)
  {
    UInt32 val32 = (UInt32)val;
    INT_TO_STR_SPEC(val32)
  }
  else
  {
    INT_TO_STR_SPEC(val)
  }

  if (i < 3)
  {
    if (i != 0)
    {
      *s++ = temp[(size_t)i - 1];
      if (i == 2)
        *s++ = temp[0];
    }
    *s = 0;
    return;
  }

  unsigned r = i % 3;
  if (r != 0)
  {
    s[0] = temp[--i];
    if (r == 2)
      s[1] = temp[--i];
    s += r;
  }

  do
  {
    s[0] = ' ';
    s[1] = temp[(size_t)i - 1];
    s[2] = temp[(size_t)i - 2];
    s[3] = temp[(size_t)i - 3];
    s += 4;
  }
  while (i -= 3);
  
  *s = 0;
}

UString ConvertSizeToString(UInt64 value);
UString ConvertSizeToString(UInt64 value)
{
  wchar_t s[32];
  ConvertSizeToString(value, s);
  return s;
}

bool IsSizeProp(UINT propID) throw();
bool IsSizeProp(UINT propID) throw()
{
  switch (propID)
  {
    case kpidSize:
    case kpidPackSize:
    case kpidNumSubDirs:
    case kpidNumSubFiles:
    case kpidOffset:
    case kpidLinks:
    case kpidNumBlocks:
    case kpidNumVolumes:
    case kpidPhySize:
    case kpidHeadersSize:
    case kpidTotalSize:
    case kpidFreeSpace:
    case kpidClusterSize:
    case kpidNumErrors:
    case kpidNumStreams:
    case kpidNumAltStreams:
    case kpidAltStreamsSize:
    case kpidVirtualSize:
    case kpidUnpackSize:
    case kpidTotalPhySize:
    case kpidTailSize:
    case kpidEmbeddedStubSize:
      return true;
  }
  return false;
}



/*
#include <stdio.h>

UInt64 GetCpuTicks()
{
    #ifdef _WIN64
      return __rdtsc();
    #else
      UInt32 lowVal, highVal;
      __asm RDTSC;
      __asm mov lowVal, EAX;
      __asm mov highVal, EDX;
      return ((UInt64)highVal << 32) | lowVal;
    #endif
}

UInt32 g_NumGroups;
UInt64 g_start_tick;
UInt64 g_prev_tick;
DWORD g_Num_SetItemText;
UInt32 g_NumMessages;
*/

LRESULT CPanel::SetItemText(LVITEMW &item)
{
  if (_dontShowMode)
    return 0;
  UInt32 realIndex = GetRealIndex(item);

  // g_Num_SetItemText++;

  /*
  if ((item.mask & LVIF_IMAGE) != 0)
  {
    bool defined  = false;
    CComPtr<IFolderGetSystemIconIndex> folderGetSystemIconIndex;
    _folder.QueryInterface(&folderGetSystemIconIndex);
    if (folderGetSystemIconIndex)
    {
      folderGetSystemIconIndex->GetSystemIconIndex(index, &item.iImage);
      defined = (item.iImage > 0);
    }
    if (!defined)
    {
      NCOM::CPropVariant prop;
      _folder->GetProperty(index, kpidAttrib, &prop);
      UINT32 attrib = 0;
      if (prop.vt == VT_UI4)
        attrib = prop.ulVal;
      else if (IsItemFolder(index))
        attrib |= FILE_ATTRIBUTE_DIRECTORY;
      if (_currentFolderPrefix.IsEmpty())
        throw 1;
      else
        item.iImage = _extToIconMap.GetIconIndex(attrib, GetSystemString(GetItemName(index)));
    }
    // item.iImage = 1;
  }
  */

  if ((item.mask & LVIF_TEXT) == 0)
    return 0;

  LPWSTR text = item.pszText;

  if (item.cchTextMax > 0)
    text[0] = 0;

  if (item.cchTextMax <= 1)
    return 0;

  // item.cchTextMax > 1
  
  const CPropColumn &property = _visibleColumns[item.iSubItem];
  PROPID propID = property.ID;

  if (realIndex == kParentIndex_UInt32)
  {
    if (propID == kpidName)
    {
      if (item.cchTextMax > 2)
      {
        text[0] = '.';
        text[1] = '.';
        text[2] = 0;
      }
    }
    return 0;
  }

  /*
  // List-view in report-view in Windows 10 is slow (50+ ms) for page change.
  // that code shows the time of page reload for items
  // if you know how to improve the speed of list view refresh, notify 7-Zip developer

  // if (propID == 2000)
  // if (propID == kpidName)
  {
    // debug column;
    // DWORD dw = GetCpuTicks();
    UInt64 dw = GetCpuTicks();
    UInt64 deltaLast = dw - g_prev_tick;
    #define conv_ticks(t) ((unsigned)((t) / 100000))
    if (deltaLast > 1000u * 1000 * 1000)
    {
      UInt64 deltaFull = g_prev_tick - g_start_tick;
      char s[128];
      sprintf(s, "%d", conv_ticks(deltaFull));
      OutputDebugStringA(s);
      g_start_tick = dw;
      g_NumGroups++;
    }
    g_prev_tick = dw;
    UString u;
    char s[128];
    UInt64 deltaFull = dw - g_start_tick;
    // for (int i = 0; i < 100000; i++)
    sprintf(s, "%d %d %d-%d ", g_NumMessages, g_Num_SetItemText, g_NumGroups, conv_ticks(deltaFull));
    // sprintf(s, "%d-%d ", g_NumGroups, conv_ticks(deltaFull));
    u = s;
    lstrcpyW(text, u.Ptr());
    text += u.Len();

    // dw = GetCpuTicks();
    // deltaFull = dw - g_prev_tick;
    // sprintf(s, "-%d ", conv_ticks(deltaFull));
    // u = s;
    // lstrcpyW(text, u.Ptr());
    // text += u.Len();

    if (propID != kpidName)
      return 0;
  }
  */


  if (property.IsRawProp)
  {
    const void *data;
    UInt32 dataSize;
    UInt32 propType;
    RINOK(_folderRawProps->GetRawProp(realIndex, propID, &data, &dataSize, &propType))
    unsigned limit = (unsigned)item.cchTextMax - 1;
    // limit != 0
    if (dataSize == 0)
      return 0;
    
    if (propID == kpidNtReparse)
    {
      UString s;
      ConvertNtReparseToString((const Byte *)data, dataSize, s);
      if (!s.IsEmpty())
      {
        unsigned i;
        for (i = 0; i < limit; i++)
        {
          const wchar_t c = s[i];
          if (c == 0)
            break;
          text[i] = c;
        }
        text[i] = 0;
        return 0;
      }
    }
    else if (propID == kpidNtSecure)
    {
      AString s;
      ConvertNtSecureToString((const Byte *)data, dataSize, s);
      if (!s.IsEmpty())
      {
        unsigned i;
        for (i = 0; i < limit; i++)
        {
          const wchar_t c = (Byte)s[i];
          if (c == 0)
            break;
          text[i] = c;
        }
        text[i] = 0;
        return 0;
      }
    }
    {
      const unsigned kMaxDataSize = 64;
      if (dataSize > kMaxDataSize)
      {
        char temp[32];
        MyStringCopy(temp, "data:");
        ConvertUInt32ToString(dataSize, temp + 5);
        unsigned i;
        for (i = 0; i < limit; i++)
        {
          wchar_t c = (Byte)temp[i];
          if (c == 0)
            break;
          *text++ = c;
        }
        *text = 0;
      }
      else
      {
        const char * const k_Hex =
          (dataSize <= 8
            && (propID == kpidCRC || propID == kpidChecksum))
            ? k_Hex_Upper : k_Hex_Lower;
        limit /= 2;
        if (limit > dataSize)
            limit = dataSize;
        const Byte *data2 = (const Byte *)data;
        do
        {
          const size_t b = *data2++;
          text[0] = (Byte)k_Hex[b >> 4];
          text[1] = (Byte)k_Hex[b & 15];
          text += 2;
        }
        while (--limit);
        *text = 0;
      }
    }
    return 0;
  }
  /*
  {
    NCOM::CPropVariant prop;
    if (propID == kpidType)
      string = GetFileType(index);
    else
    {
      HRESULT result = m_ArchiveFolder->GetProperty(index, propID, &prop);
      if (result != S_OK)
      {
        // PrintMessage("GetPropertyValue error");
        return 0;
      }
      string = ConvertPropertyToString(prop, propID, false);
    }
  }
  */
  // const NFind::CFileInfo &aFileInfo = m_Files[index];

  NCOM::CPropVariant prop;
  /*
  bool needRead = true;
  if (propID == kpidSize)
  {
    CComPtr<IFolderGetItemFullSize> getItemFullSize;
    if (_folder.QueryInterface(&getItemFullSize) == S_OK)
    {
      if (getItemFullSize->GetItemFullSize(index, &prop) == S_OK)
        needRead = false;
    }
  }
  if (needRead)
  */

  if (item.cchTextMax < 32)
    return 0;

  if (propID == kpidName)
  {
    if (_folderGetItemName)
    {
      const wchar_t *name = NULL;
      unsigned nameLen = 0;
      _folderGetItemName->GetItemName(realIndex, &name, &nameLen);
      
      if (name)
      {
        unsigned dest = 0;
        const unsigned limit = (unsigned)item.cchTextMax - 1;
        
        for (unsigned i = 0; dest < limit;)
        {
          const wchar_t c = name[i++];
          if (c == 0)
            break;
          text[dest++] = c;
          
          if (c != ' ')
          {
            if (c != 0x202E) // RLO
              continue;
            text[(size_t)dest - 1] = '_';
            continue;
          }
          
          if (name[i] != ' ')
            continue;
          
          unsigned t = 1;
          for (; name[i + t] == ' '; t++);

          if (t >= 4 && dest + 4 < limit)
          {
            text[dest++] = '.';
            text[dest++] = '.';
            text[dest++] = '.';
            text[dest++] = ' ';
            i += t;
          }
        }

        if (dest == 0)
          text[dest++]= '_';

        #ifdef _WIN32
        else if (text[(size_t)dest - 1] == ' ')
        {
          if (dest < limit)
            text[dest++] = SPACE_TERMINATOR_CHAR;
          else
            text[dest - 1] = SPACE_REPLACE_CHAR;
        }
        #endif

        text[dest] = 0;
        // OutputDebugStringW(text);
        return 0;
      }
    }
  }
  
  if (propID == kpidPrefix)
  {
    if (_folderGetItemName)
    {
      const wchar_t *name = NULL;
      unsigned nameLen = 0;
      _folderGetItemName->GetItemPrefix(realIndex, &name, &nameLen);
      if (name)
      {
        unsigned dest = 0;
        const unsigned limit = (unsigned)item.cchTextMax - 1;
        for (unsigned i = 0; dest < limit;)
        {
          const wchar_t c = name[i++];
          if (c == 0)
            break;
          text[dest++] = c;
        }
        text[dest] = 0;
        return 0;
      }
    }
  }
  
  const HRESULT res = _folder->GetProperty(realIndex, propID, &prop);
  
  if (res != S_OK)
  {
    MyStringCopy(text, L"Error: ");
    // s = UString("Error: ") + HResultToMessage(res);
  }
  else if ((prop.vt == VT_UI8 || prop.vt == VT_UI4 || prop.vt == VT_UI2) && IsSizeProp(propID))
  {
    UInt64 v = 0;
    ConvertPropVariantToUInt64(prop, v);
    ConvertSizeToString(v, text);
  }
  else if (prop.vt == VT_BSTR)
  {
    const unsigned limit = (unsigned)item.cchTextMax - 1;
    const wchar_t *src = prop.bstrVal;
    unsigned i;
    for (i = 0; i < limit; i++)
    {
      wchar_t c = src[i];
      if (c == 0) break;
      if (c == 0xA) c = ' ';
      if (c == 0xD) c = ' ';
      text[i] = c;
    }
    text[i] = 0;
  }
  else
  {
    char temp[64];
    ConvertPropertyToShortString2(temp, prop, propID, _timestampLevel);
    unsigned i;
    const unsigned limit = (unsigned)item.cchTextMax - 1;
    for (i = 0; i < limit; i++)
    {
      const wchar_t c = (Byte)temp[i];
      if (c == 0)
        break;
      text[i] = c;
    }
    text[i] = 0;
  }
  
  return 0;
}

void CPanel::OnItemChanged(NMLISTVIEW *item)
{
  const unsigned index = (unsigned)item->lParam;
  if (index == kParentIndex)
    return;
  const bool oldSelected = (item->uOldState & LVIS_SELECTED) != 0;
  const bool newSelected = (item->uNewState & LVIS_SELECTED) != 0;
  // Don't change this code. It works only with such check
  if (oldSelected != newSelected)
    _selectedStatusVector[index] = newSelected;
}

extern bool g_LVN_ITEMACTIVATE_Support;

void CPanel::OnNotifyActivateItems()
{
  bool alt = IsKeyDown(VK_MENU);
  bool ctrl = IsKeyDown(VK_CONTROL);
  bool shift = IsKeyDown(VK_SHIFT);
  if (!shift && alt && !ctrl)
    Properties();
  else
    OpenSelectedItems(!shift || alt || ctrl);
}

bool CPanel::OnNotifyList(LPNMHDR header, LRESULT &result)
{
  switch (header->code)
  {
    case LVN_ITEMCHANGED:
    {
      if (_enableItemChangeNotify)
      {
        if (!_mySelectMode)
          OnItemChanged((LPNMLISTVIEW)header);
        
        // Post_Refresh_StatusBar();
        /* 9.26: we don't call Post_Refresh_StatusBar.
           it was very slow if we select big number of files
           and then clead slection by selecting just new file.
           probably it called slow Refresh_StatusBar for each item deselection.
           I hope Refresh_StatusBar still will be called for each key / mouse action.
        */
      }
      return false;
    }
    /*

    case LVN_ODSTATECHANGED:
      {
      break;
      }
    */

    case LVN_GETDISPINFOW:
    {
      LV_DISPINFOW *dispInfo = (LV_DISPINFOW *)header;

      //is the sub-item information being requested?

      if ((dispInfo->item.mask & LVIF_TEXT) != 0 ||
          (dispInfo->item.mask & LVIF_IMAGE) != 0)
        SetItemText(dispInfo->item);
      {
        // 20.03:
        result = 0;
        return true;
        // old 7-Zip:
        // return false;
      }
    }
    case LVN_KEYDOWN:
    {
      LPNMLVKEYDOWN keyDownInfo = LPNMLVKEYDOWN(header);
      bool boolResult = OnKeyDown(keyDownInfo, result);
      switch (keyDownInfo->wVKey)
      {
        case VK_CONTROL:
        case VK_SHIFT:
        case VK_MENU:
          break;
        default:
          Post_Refresh_StatusBar();
      }
      return boolResult;
    }

    case LVN_COLUMNCLICK:
      OnColumnClick(LPNMLISTVIEW(header));
      return false;

    case LVN_ITEMACTIVATE:
      if (g_LVN_ITEMACTIVATE_Support)
      {
        OnNotifyActivateItems();
        return false;
      }
      break;
    case NM_DBLCLK:
    case NM_RETURN:
      if (!g_LVN_ITEMACTIVATE_Support)
      {
        OnNotifyActivateItems();
        return false;
      }
      break;

    case NM_RCLICK:
      Post_Refresh_StatusBar();
      break;

    /*
      return OnRightClick((LPNMITEMACTIVATE)header, result);
    */
      /*
      case NM_CLICK:
      SendRefreshStatusBarMessage();
      return 0;
      
        // TODO : Handler default action...
        return 0;
        case LVN_ITEMCHANGED:
        {
        NMLISTVIEW *pNMLV = (NMLISTVIEW *) lpnmh;
        SelChange(pNMLV);
        return TRUE;
        }
        case NM_SETFOCUS:
        return onSetFocus(NULL);
        case NM_KILLFOCUS:
        return onKillFocus(NULL);
      */
    case NM_CLICK:
    {
      // we need SetFocusToList, if we drag-select items from other panel.
      SetFocusToList();
      Post_Refresh_StatusBar();
      if (_mySelectMode)
#ifdef Z7_USE_DYN_ComCtl32Version
        if (g_ComCtl32Version >= MAKELONG(71, 4))
#endif
          OnLeftClick((MY_NMLISTVIEW_NMITEMACTIVATE *)header);
      return false;
    }
    case LVN_BEGINLABELEDITW:
      result = OnBeginLabelEdit((LV_DISPINFOW *)header);
      return true;
    case LVN_ENDLABELEDITW:
      result = OnEndLabelEdit((LV_DISPINFOW *)header);
      return true;

    case NM_CUSTOMDRAW:
    {
      if (_mySelectMode || (_markDeletedItems && _thereAreDeletedItems))
        return OnCustomDraw((LPNMLVCUSTOMDRAW)header, result);
      break;
    }
    case LVN_BEGINDRAG:
    {
      OnDrag((LPNMLISTVIEW)header, false);
      Post_Refresh_StatusBar();
      break;
    }
    case LVN_BEGINRDRAG:
    {
      OnDrag((LPNMLISTVIEW)header, true);
      Post_Refresh_StatusBar();
      break;
    }
    // case LVN_BEGINRDRAG:
  }
  return false;
}

bool CPanel::OnCustomDraw(LPNMLVCUSTOMDRAW lplvcd, LRESULT &result)
{
  switch (lplvcd->nmcd.dwDrawStage)
  {
  case CDDS_PREPAINT :
    result = CDRF_NOTIFYITEMDRAW;
    return true;
    
  case CDDS_ITEMPREPAINT:
    /*
    SelectObject(lplvcd->nmcd.hdc,
    GetFontForItem(lplvcd->nmcd.dwItemSpec,
    lplvcd->nmcd.lItemlParam) );
    lplvcd->clrText = GetColorForItem(lplvcd->nmcd.dwItemSpec,
    lplvcd->nmcd.lItemlParam);
    lplvcd->clrTextBk = GetBkColorForItem(lplvcd->nmcd.dwItemSpec,
    lplvcd->nmcd.lItemlParam);
    */
    const unsigned realIndex = (unsigned)lplvcd->nmcd.lItemlParam;
    lplvcd->clrTextBk = _listView.GetBkColor();
    if (_mySelectMode)
    {
      if (realIndex != kParentIndex && _selectedStatusVector[realIndex])
       lplvcd->clrTextBk = RGB(255, 192, 192);
    }

    if (_markDeletedItems && _thereAreDeletedItems)
    {
      if (IsItem_Deleted(realIndex))
        lplvcd->clrText = RGB(255, 0, 0);
    }
    // lplvcd->clrText = RGB(0, 0, 0);
    // result = CDRF_NEWFONT;
    result = CDRF_NOTIFYITEMDRAW;
    return true;
    
    // return false;
    // return true;
    /*
    case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
    if (lplvcd->iSubItem == 0)
    {
    // lplvcd->clrText = RGB(255, 0, 0);
    lplvcd->clrTextBk = RGB(192, 192, 192);
    }
    else
    {
    lplvcd->clrText = RGB(0, 0, 0);
    lplvcd->clrTextBk = RGB(255, 255, 255);
    }
    return true;
    */

        /* At this point, you can change the background colors for the item
        and any subitems and return CDRF_NEWFONT. If the list-view control
        is in report mode, you can simply return CDRF_NOTIFYSUBITEMREDRAW
        to customize the item's subitems individually */
  }
  return false;
}

void CPanel::Refresh_StatusBar()
{
  /*
  g_name_cnt++;
  char s[256];
  sprintf(s, "g_name_cnt = %8d", g_name_cnt);
  OutputDebugStringA(s);
  */
  // DWORD dw = GetTickCount();

  CRecordVector<UInt32> indices;
  Get_ItemIndices_Operated(indices);

  {
    UString s;
    s.Add_UInt32(indices.Size());
    s += " / ";
    s.Add_UInt32(_selectedStatusVector.Size());

    // UString s1 = MyFormatNew(g_App.LangString_N_SELECTED_ITEMS, NumberToString(indices.Size()));
    // UString s1 = MyFormatNew(IDS_N_SELECTED_ITEMS, NumberToString(indices.Size()));
    _statusBar.SetText(0, MyFormatNew(g_App.LangString_N_SELECTED_ITEMS, s));
    // _statusBar.SetText(0, MyFormatNew(IDS_N_SELECTED_ITEMS, NumberToString(indices.Size())));
  }

  {
    wchar_t selectSizeString[32];
    selectSizeString[0] = 0;
    
    if (!indices.IsEmpty())
    {
      // for (unsigned ttt = 0; ttt < 1000; ttt++) {
      UInt64 totalSize = 0;
      FOR_VECTOR (i, indices)
        totalSize += GetItemSize(indices[i]);
      ConvertSizeToString(totalSize, selectSizeString);
      // }
    }
    _statusBar.SetText(1, selectSizeString);
  }

  const int focusedItem = _listView.GetFocusedItem();
  wchar_t sizeString[32];
  sizeString[0] = 0;
  wchar_t dateString[32];
  dateString[0] = 0;
  if (focusedItem >= 0 && _listView.GetSelectedCount() > 0)
  {
    const unsigned realIndex = GetRealItemIndex(focusedItem);
    if (realIndex != kParentIndex)
    {
      ConvertSizeToString(GetItemSize(realIndex), sizeString);
      NCOM::CPropVariant prop;
      if (_folder->GetProperty(realIndex, kpidMTime, &prop) == S_OK)
      {
        char dateString2[64];
        dateString2[0] = 0;
        ConvertPropertyToShortString2(dateString2, prop, kpidMTime);
        for (unsigned i = 0;; i++)
        {
          char c = dateString2[i];
          dateString[i] = (Byte)c;
          if (c == 0)
            break;
        }
      }
    }
  }
  _statusBar.SetText(2, sizeString);
  _statusBar.SetText(3, dateString);
  
  // _statusBar.SetText(4, nameString);
  // _statusBar2.SetText(1, MyFormatNew(L"{0} bytes", NumberToStringW(totalSize)));
  // }
  /*
  dw = GetTickCount() - dw;
  sprintf(s, "status = %8d ms", dw);
  OutputDebugStringA(s);
  */
}
