/*
 * regexpl - Console Registry Explorer
 *
 * Copyright (C) 2000-2005 Nedko Arnaudov <nedko@users.sourceforge.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

// Completion.cpp : Defines the completion related functions.
#include "ph.h"
#include "RegistryKey.h"
#include "RegistryTree.h"

#define COMPLETION_BUFFER_SIZE	4096

extern CRegistryTree Tree;

BOOL g_blnCompletionCycle = TRUE;

class CCompletionMatch
{
public:
  CCompletionMatch()
  {
    m_pNext = m_pPrev = NULL;
    m_pszText = NULL;
  };

  BOOL Init(const TCHAR *pszText)
  {
    // find the offset to "unique" part
    const TCHAR *pszUnique = _tcsrchr(pszText,_T('\\'));
    pszUnique = pszUnique?(pszUnique+1):pszText;
    BOOL b = _tcschr(pszUnique,_T(' ')) != NULL; // has it spaces in it ???
    size_t s = _tcslen(pszText);

    if (m_pszText)
      delete[] m_pszText;

    m_pszText = new (std::nothrow) TCHAR [s+(b?3:1)]; // if we have spaces in unique part, we need 2 addtional chars for "

    if (!m_pszText)
      return FALSE;

    ASSERT(pszText <= pszUnique);
    s = pszUnique - pszText; // calculate offset of the unique part
    if (s)
      _tcsncpy(m_pszText,pszText,s);

    if (b)
      m_pszText[s++] = _T('\"');

    _tcscpy(m_pszText+s,pszUnique);

    if (b)
      _tcscat(m_pszText,_T("\""));

    return TRUE;
  }

  ~CCompletionMatch()
  {
    if (m_pszText)
      delete[] m_pszText;
  }

private:
  TCHAR *m_pszText;
  BOOL m_blnIsKey;
  CCompletionMatch *m_pNext;
  CCompletionMatch *m_pPrev;
  friend class CCompletionList;
};

class CCompletionList
{
public:
  CCompletionList();
  ~CCompletionList();

  BOOL IsNewCompletion(const TCHAR *pszContext, const TCHAR *pszBegin, BOOL& rblnNew);
  BOOL Add(const TCHAR *pszText, BOOL blnIsKey);
  const TCHAR *Get(unsigned __int64 nIndex, BOOL& rblnIsKey);
  unsigned __int64 GetCount();
  const TCHAR * GetContext();
  const TCHAR * GetBegin();
  void DeleteList();
  void Invalidate();

private:
  CCompletionMatch *m_pHead; // head of completions linked list
  CCompletionMatch *m_pTail; // tail of completions linked list
  CCompletionMatch *m_pLastSearched;
  unsigned int m_nLastSearched;
  TCHAR *m_pszContext;
  TCHAR *m_pszBegin;
  TCHAR *m_pszCurrentKey;
  unsigned __int64 m_nCount;
} g_Completion;

// --- begin of CCompletionList implementation ---
CCompletionList::CCompletionList()
{
  m_pHead = NULL;
  m_pTail = NULL;
  m_nCount = 0;
  m_pLastSearched = NULL;
  m_pszContext = NULL;
  m_pszBegin = NULL;
  m_pszCurrentKey = NULL;
}

CCompletionList::~CCompletionList()
{
  DeleteList();

  if (m_pszContext)
    delete[] m_pszContext;

  if (m_pszBegin)
    delete[] m_pszBegin;

  if (m_pszCurrentKey)
    delete[] m_pszCurrentKey;
}

void CCompletionList::Invalidate()
{
  if (m_pszCurrentKey)
  {
    delete[] m_pszCurrentKey;
    m_pszCurrentKey = NULL;
  }
}

BOOL CCompletionList::IsNewCompletion(const TCHAR *pszContext, const TCHAR *pszBegin, BOOL& rblnNew)
{
  const TCHAR *pszCurrentKey = Tree.GetCurrentPath();
  if (!m_pszContext ||
      !m_pszBegin ||
      !m_pszCurrentKey ||
      (_tcscmp(m_pszContext,pszContext) != 0) ||
      (_tcscmp(m_pszBegin,pszBegin) != 0) ||
      (_tcscmp(m_pszCurrentKey,pszCurrentKey)))
  { // new completion
    DeleteList();
    rblnNew = TRUE;
    if (m_pszContext)
    {
      delete[] m_pszContext;
      m_pszContext = NULL;
    }

    if (m_pszBegin)
    {
      delete[] m_pszBegin;
      m_pszBegin = NULL;
    }

    if (m_pszCurrentKey)
    {
      delete[] m_pszCurrentKey;
      m_pszCurrentKey = NULL;
    }

    size_t s = _tcslen(pszContext);
    m_pszContext = new (std::nothrow) TCHAR[s+1];
    if (!m_pszContext)
      return FALSE;
    _tcscpy(m_pszContext,pszContext);

    s = _tcslen(pszBegin);
    m_pszBegin = new (std::nothrow) TCHAR[s+1];
    if (!m_pszBegin)
    {
      delete[] m_pszContext;
      m_pszContext = NULL;
      return FALSE;
    }
    _tcscpy(m_pszBegin,pszBegin);

    s = _tcslen(pszCurrentKey);
    m_pszCurrentKey = new (std::nothrow) TCHAR[s+1];
    if (!m_pszCurrentKey)
    {
      delete[] m_pszContext;
      delete[] m_pszBegin;
      m_pszContext = NULL;
      m_pszBegin = NULL;
      return FALSE;
    }
    _tcscpy(m_pszCurrentKey,pszCurrentKey);

    return TRUE;
  }

  rblnNew = FALSE;
  return TRUE;
}

BOOL CCompletionList::Add(const TCHAR *pszText, BOOL blnIsKey)
{
  if (_tcsnicmp(pszText,m_pszBegin,_tcslen(m_pszBegin)) != 0)
    return TRUE;
  CCompletionMatch *pNode = new (std::nothrow) CCompletionMatch;
  if (!pNode)
    return FALSE;
  if (!pNode->Init(pszText))
    return FALSE;

  ASSERT(pNode->m_pszText);
  ASSERT(pNode->m_pNext == NULL);

  // add new node to tail
  pNode->m_blnIsKey = blnIsKey;
  if (m_pTail)
  {
    pNode->m_pPrev = m_pTail;
    ASSERT(m_pTail->m_pNext == NULL);
    m_pTail->m_pNext = pNode;
    m_pTail = pNode;
  }
  else
  {
    ASSERT(m_pHead == NULL);
    m_pHead = m_pTail = pNode;
  }

  m_nCount++;

  m_pLastSearched = NULL;

  return TRUE;
}

const TCHAR * CCompletionList::Get(unsigned __int64 nIndex, BOOL& rblnIsKey)
{
  ASSERT(nIndex < m_nCount);
  BOOL blnForward = FALSE;
  CCompletionMatch *pNode = NULL;

  unsigned __int64 nRelativeIndex = 0;

  if (m_pLastSearched)
  {
    pNode = m_pLastSearched;
    blnForward = nIndex > m_nLastSearched;
    nRelativeIndex = blnForward?(nIndex-m_nLastSearched):(m_nLastSearched-nIndex);
    if ((nRelativeIndex > nIndex)||(nRelativeIndex > m_nCount-nIndex-1))
      pNode = NULL; // seraching from tail or from head is more effective
  }

  if (!pNode && (nIndex <= m_nCount/2))
  { // search from head
    pNode = m_pHead;
    blnForward = TRUE;
    nRelativeIndex = nIndex;
  }

  if (!pNode)
  { // search from tail
    pNode = m_pTail;
    blnForward = FALSE;
    nRelativeIndex = m_nCount-nIndex-1;
  }

  while(pNode)
  {
    if (nRelativeIndex == 0)
    {
      m_nLastSearched = nIndex;
      m_pLastSearched = pNode;
      rblnIsKey = pNode->m_blnIsKey;
      if (!pNode->m_pszText)
        ASSERT(FALSE);
      return pNode->m_pszText;
    }

    nRelativeIndex--;

    pNode = blnForward?(pNode->m_pNext):(pNode->m_pPrev);
  }

  ASSERT(FALSE);
  return NULL;
}

unsigned __int64 CCompletionList::GetCount()
{
  return m_nCount;
}

const TCHAR * CCompletionList::GetContext()
{
  return m_pszContext;
}

const TCHAR * CCompletionList::GetBegin()
{
  return m_pszBegin;
}

void CCompletionList::DeleteList()
{
  CCompletionMatch *pNode;
  while(m_pHead)
  {
    pNode = m_pHead;
    m_pHead = m_pHead->m_pNext;
    delete pNode;
  }
  m_pTail = NULL;
  ASSERT(m_pHead == NULL);
  m_nCount = 0;
}

// --- end of CCompletionList implementation ---

BOOL FillCompletion(const TCHAR *pszKey)
{
  g_Completion.DeleteList();
  LONG nError;
  CRegistryKey Key;
  TCHAR *pszSubkeyName = NULL;
  DWORD dwMaxSubkeyNameLength;
  TCHAR *pszValueName = NULL;
  DWORD dwMaxValueNameSize;

  if (!Tree.GetKey(pszKey?pszKey:_T("."),KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,Key))
    return FALSE;

  BOOL blnCompletionOnKeys = TRUE;
  BOOL blnCompletionOnValues = TRUE;

/*	if ((_tcsnicmp(pchContext,DIR_CMD,DIR_CMD_LENGTH) == 0)||
		(_tcsnicmp(pchContext,CD_CMD,CD_CMD_LENGTH) == 0)||
		(_tcsnicmp(pchContext,OWNER_CMD,OWNER_CMD_LENGTH) == 0)||
		(_tcsnicmp(pchContext,DACL_CMD,DACL_CMD_LENGTH) == 0)||
		(_tcsnicmp(pchContext,SACL_CMD,SACL_CMD_LENGTH) == 0))
	{
		blnCompletionOnValues = FALSE;
	}*/
//	else if (_tcsnicmp(pchContext,VALUE_CMD,VALUE_CMD_LENGTH) == 0)
//	{
//		blnCompletionOnKeys = FALSE;
//	}

  size_t nKeyNameSize = 0;
  if (pszKey)
  {
    nKeyNameSize = _tcslen(pszKey);
    if (_tcscmp(pszKey,_T("\\")))
      nKeyNameSize++;
  }

  if (blnCompletionOnKeys)
  {
    nError = Key.GetSubkeyNameMaxLength(dwMaxSubkeyNameLength);
    if (nError != ERROR_SUCCESS)
      return FALSE;

    pszSubkeyName = new (std::nothrow) TCHAR[nKeyNameSize+dwMaxSubkeyNameLength+1];
    if (!pszSubkeyName)
      goto Abort;

    if (pszKey)
      _stprintf(pszSubkeyName,_tcscmp(pszKey,_T("\\"))?_T("%s\\"):_T("%s"),pszKey);

    Key.InitSubkeyEnumeration(pszSubkeyName+nKeyNameSize,dwMaxSubkeyNameLength);
    while ((nError = Key.GetNextSubkeyName()) == ERROR_SUCCESS)
      if (!g_Completion.Add(pszSubkeyName,TRUE))
      {
        ASSERT(FALSE);
        goto Abort;
      }
    if ((nError != ERROR_SUCCESS)&&(nError != ERROR_NO_MORE_ITEMS))
    {
      ASSERT(FALSE);
      goto Abort;
    }
  }

  if (blnCompletionOnValues)
  {
    nError = Key.GetMaxValueNameLength(dwMaxValueNameSize);
    if (nError != ERROR_SUCCESS)
    {
      ASSERT(FALSE);
      goto Abort;
    }

    pszValueName = new (std::nothrow) TCHAR[nKeyNameSize+dwMaxValueNameSize+1];
    if (!pszValueName)
      goto Abort;

    if (pszKey)
      _stprintf(pszValueName,_tcscmp(pszKey,_T("\\"))?_T("%s\\"):_T("%s"),pszKey);

    Key.InitValueEnumeration(pszValueName+nKeyNameSize,dwMaxValueNameSize,NULL,0,NULL);
    while((nError = Key.GetNextValue()) == ERROR_SUCCESS)
      if (!g_Completion.Add(pszValueName,FALSE))
      {
        ASSERT(FALSE);
        goto Abort;
      }
    if ((nError != ERROR_SUCCESS)&&(nError != ERROR_NO_MORE_ITEMS))
    {
      ASSERT(FALSE);
      goto Abort;
    }
  }

  if (pszValueName)
    delete[] pszValueName;
  if (pszSubkeyName)
    delete[] pszSubkeyName;
  return TRUE;
Abort:
  if (pszValueName)
    delete[] pszValueName;
  if (pszSubkeyName)
    delete[] pszSubkeyName;
  return FALSE;
}

const TCHAR * CompletionCallback(unsigned __int64 & rnIndex,
                                 const BOOL *pblnForward,
                                 const TCHAR *pszContext,
                                 const TCHAR *pszBegin)
{
  static TCHAR pszBuffer[COMPLETION_BUFFER_SIZE];

  // Find first non-white space in context
  while(*pszContext && _istspace(*pszContext))
    pszContext++;

  BOOL blnNewCompletion = TRUE;
  if (!g_Completion.IsNewCompletion(pszContext,pszBegin,blnNewCompletion))
  {
    ASSERT(FALSE);
    return NULL;
  }

  if (blnNewCompletion)
  {
    _tcsncpy(pszBuffer,pszBegin,COMPLETION_BUFFER_SIZE-1);
    pszBuffer[COMPLETION_BUFFER_SIZE-1] = 0;
    TCHAR *pszSeparator = pszBuffer; // set it to aby non null value
    if (_tcscmp(pszBuffer,_T("\\")))
    {
      pszSeparator = _tcsrchr(pszBuffer,_T('\\'));
      if (pszSeparator)
        *pszSeparator = 0;
    }

    if (!FillCompletion(pszSeparator?pszBuffer:NULL))
      return NULL;
  }

  unsigned __int64 nTotalItems = g_Completion.GetCount();
  if (nTotalItems == 0)
    return NULL;

  if (rnIndex >= nTotalItems)
    rnIndex = nTotalItems-1;

  if (pblnForward)
  {
    if (*pblnForward)
    {
      rnIndex++;
      if (rnIndex >= nTotalItems)
      {
        if (g_blnCompletionCycle)
          rnIndex = 0;
        else
          rnIndex--;
      }
    }
    else
    {
      if (rnIndex)
        rnIndex--;
      else if (g_blnCompletionCycle)
        rnIndex = nTotalItems-1;
    }
  }
  BOOL blnIsKey = FALSE;
  const TCHAR *pszName = g_Completion.Get(rnIndex,blnIsKey);

  ASSERT(pszName);

  _sntprintf(pszBuffer,COMPLETION_BUFFER_SIZE-1,_T("%s%s"),pszName,(blnIsKey?_T("\\"):_T("")));
  pszBuffer[COMPLETION_BUFFER_SIZE-1] = 0;
  return pszBuffer;
}

void InvalidateCompletion()
{
  g_Completion.Invalidate();
}