// Common/Wildcard.cpp

#include "StdAfx.h"

#include "Wildcard.h"

extern
bool g_CaseSensitive;
bool g_CaseSensitive =
  #if defined(_WIN32) || defined(__DOS__)
    false;
  #elif defined (__APPLE__)
    #ifdef TARGET_OS_IPHONE
      true;
    #else
      false;
    #endif
  #else
    true;
  #endif


bool IsPath1PrefixedByPath2(const wchar_t *s1, const wchar_t *s2)
{
  if (g_CaseSensitive)
    return IsString1PrefixedByString2(s1, s2);
  return IsString1PrefixedByString2_NoCase(s1, s2);
}

// #include <stdio.h>

/*
static int MyStringCompare_PathLinux(const wchar_t *s1, const wchar_t *s2) throw()
{
  for (;;)
  {
    wchar_t c1 = *s1++;
    wchar_t c2 = *s2++;
    if (c1 != c2)
    {
      if (c1 == 0) return -1;
      if (c2 == 0) return 1;
      if (c1 == '/') c1 = 0;
      if (c2 == '/') c2 = 0;
      if (c1 < c2) return -1;
      if (c1 > c2) return 1;
      continue;
    }
    if (c1 == 0) return 0;
  }
}
*/

static int MyStringCompare_Path(const wchar_t *s1, const wchar_t *s2) throw()
{
  for (;;)
  {
    wchar_t c1 = *s1++;
    wchar_t c2 = *s2++;
    if (c1 != c2)
    {
      if (c1 == 0) return -1;
      if (c2 == 0) return 1;
      if (IS_PATH_SEPAR(c1)) c1 = 0;
      if (IS_PATH_SEPAR(c2)) c2 = 0;
      if (c1 < c2) return -1;
      if (c1 > c2) return 1;
      continue;
    }
    if (c1 == 0) return 0;
  }
}

static int MyStringCompareNoCase_Path(const wchar_t *s1, const wchar_t *s2) throw()
{
  for (;;)
  {
    wchar_t c1 = *s1++;
    wchar_t c2 = *s2++;
    if (c1 != c2)
    {
      if (c1 == 0) return -1;
      if (c2 == 0) return 1;
      if (IS_PATH_SEPAR(c1)) c1 = 0;
      if (IS_PATH_SEPAR(c2)) c2 = 0;
      c1 = MyCharUpper(c1);
      c2 = MyCharUpper(c2);
      if (c1 < c2) return -1;
      if (c1 > c2) return 1;
      continue;
    }
    if (c1 == 0) return 0;
  }
}

int CompareFileNames(const wchar_t *s1, const wchar_t *s2) STRING_UNICODE_THROW
{
  /*
  printf("\nCompareFileNames");
  printf("\n S1: %ls", s1);
  printf("\n S2: %ls", s2);
  printf("\n");
  */
  // 21.07 : we parse PATH_SEPARATOR so: 0 < PATH_SEPARATOR < 1
  if (g_CaseSensitive)
    return MyStringCompare_Path(s1, s2);
  return MyStringCompareNoCase_Path(s1, s2);
}

#ifndef USE_UNICODE_FSTRING
int CompareFileNames(const char *s1, const char *s2)
{
  const UString u1 = fs2us(s1);
  const UString u2 = fs2us(s2);
  return CompareFileNames(u1, u2);
}
#endif

// -----------------------------------------
// this function compares name with mask
// ? - any char
// * - any char or empty

static bool EnhancedMaskTest(const wchar_t *mask, const wchar_t *name)
{
  for (;;)
  {
    const wchar_t m = *mask;
    const wchar_t c = *name;
    if (m == 0)
      return (c == 0);
    if (m == '*')
    {
      if (EnhancedMaskTest(mask + 1, name))
        return true;
      if (c == 0)
        return false;
    }
    else
    {
      if (m == '?')
      {
        if (c == 0)
          return false;
      }
      else if (m != c)
        if (g_CaseSensitive || MyCharUpper(m) != MyCharUpper(c))
          return false;
      mask++;
    }
    name++;
  }
}

// --------------------------------------------------
// Splits path to strings

void SplitPathToParts(const UString &path, UStringVector &pathParts)
{
  pathParts.Clear();
  unsigned len = path.Len();
  if (len == 0)
    return;
  UString name;
  unsigned prev = 0;
  for (unsigned i = 0; i < len; i++)
    if (IsPathSepar(path[i]))
    {
      name.SetFrom(path.Ptr(prev), i - prev);
      pathParts.Add(name);
      prev = i + 1;
    }
  name.SetFrom(path.Ptr(prev), len - prev);
  pathParts.Add(name);
}

void SplitPathToParts_2(const UString &path, UString &dirPrefix, UString &name)
{
  const wchar_t *start = path;
  const wchar_t *p = start + path.Len();
  for (; p != start; p--)
    if (IsPathSepar(*(p - 1)))
      break;
  dirPrefix.SetFrom(path, (unsigned)(p - start));
  name = p;
}

void SplitPathToParts_Smart(const UString &path, UString &dirPrefix, UString &name)
{
  const wchar_t *start = path;
  const wchar_t *p = start + path.Len();
  if (p != start)
  {
    if (IsPathSepar(*(p - 1)))
      p--;
    for (; p != start; p--)
      if (IsPathSepar(*(p - 1)))
        break;
  }
  dirPrefix.SetFrom(path, (unsigned)(p - start));
  name = p;
}

/*
UString ExtractDirPrefixFromPath(const UString &path)
{
  return path.Left(path.ReverseFind_PathSepar() + 1));
}
*/

UString ExtractFileNameFromPath(const UString &path)
{
  return UString(path.Ptr((unsigned)(path.ReverseFind_PathSepar() + 1)));
}


bool DoesWildcardMatchName(const UString &mask, const UString &name)
{
  return EnhancedMaskTest(mask, name);
}

bool DoesNameContainWildcard(const UString &path)
{
  for (unsigned i = 0; i < path.Len(); i++)
  {
    wchar_t c = path[i];
    if (c == '*' || c == '?')
      return true;
  }
  return false;
}


// ----------------------------------------------------------'
// NWildcard

namespace NWildcard {

/*

M = MaskParts.Size();
N = TestNameParts.Size();

                           File                          Dir
ForFile     rec   M<=N  [N-M, N)                          -
!ForDir  nonrec   M=N   [0, M)                            -
 
ForDir      rec   M<N   [0, M) ... [N-M-1, N-1)  same as ForBoth-File
!ForFile nonrec         [0, M)                   same as ForBoth-File

ForFile     rec   m<=N  [0, M) ... [N-M, N)      same as ForBoth-File
ForDir   nonrec         [0, M)                   same as ForBoth-File

*/

bool CItem::AreAllAllowed() const
{
  return ForFile && ForDir && WildcardMatching && PathParts.Size() == 1 && PathParts.Front() == L"*";
}

bool CItem::CheckPath(const UStringVector &pathParts, bool isFile) const
{
  if (!isFile && !ForDir)
    return false;

  /*
  if (PathParts.IsEmpty())
  {
    // PathParts.IsEmpty() means all items (universal wildcard)
    if (!isFile)
      return true;
    if (pathParts.Size() <= 1)
      return ForFile;
    return (ForDir || Recursive && ForFile);
  }
  */

  int delta = (int)pathParts.Size() - (int)PathParts.Size();
  if (delta < 0)
    return false;
  int start = 0;
  int finish = 0;
  
  if (isFile)
  {
    if (!ForDir)
    {
      if (Recursive)
        start = delta;
      else if (delta !=0)
        return false;
    }
    if (!ForFile && delta == 0)
      return false;
  }
  
  if (Recursive)
  {
    finish = delta;
    if (isFile && !ForFile)
      finish = delta - 1;
  }
  
  for (int d = start; d <= finish; d++)
  {
    unsigned i;
    for (i = 0; i < PathParts.Size(); i++)
    {
      if (WildcardMatching)
      {
        if (!DoesWildcardMatchName(PathParts[i], pathParts[i + (unsigned)d]))
          break;
      }
      else
      {
        if (CompareFileNames(PathParts[i], pathParts[i + (unsigned)d]) != 0)
          break;
      }
    }
    if (i == PathParts.Size())
      return true;
  }
  return false;
}

bool CCensorNode::AreAllAllowed() const
{
  if (!Name.IsEmpty() ||
      !SubNodes.IsEmpty() ||
      !ExcludeItems.IsEmpty() ||
      IncludeItems.Size() != 1)
    return false;
  return IncludeItems.Front().AreAllAllowed();
}

int CCensorNode::FindSubNode(const UString &name) const
{
  FOR_VECTOR (i, SubNodes)
    if (CompareFileNames(SubNodes[i].Name, name) == 0)
      return (int)i;
  return -1;
}

void CCensorNode::AddItemSimple(bool include, CItem &item)
{
  CObjectVector<CItem> &items = include ? IncludeItems : ExcludeItems;
  items.Add(item);
}

void CCensorNode::AddItem(bool include, CItem &item, int ignoreWildcardIndex)
{
  if (item.PathParts.Size() <= 1)
  {
    if (item.PathParts.Size() != 0 && item.WildcardMatching)
    {
      if (!DoesNameContainWildcard(item.PathParts.Front()))
        item.WildcardMatching = false;
    }
    AddItemSimple(include, item);
    return;
  }

  const UString &front = item.PathParts.Front();
  
  // WIN32 doesn't support wildcards in file names
  if (item.WildcardMatching
      && ignoreWildcardIndex != 0
      && DoesNameContainWildcard(front))
  {
    AddItemSimple(include, item);
    return;
  }
  CCensorNode &subNode = Find_SubNode_Or_Add_New(front);
  item.PathParts.Delete(0);
  subNode.AddItem(include, item, ignoreWildcardIndex - 1);
}

/*
void CCensorNode::AddItem(bool include, const UString &path, const CCensorPathProps &props)
{
  CItem item;
  SplitPathToParts(path, item.PathParts);
  item.Recursive = props.Recursive;
  item.ForFile = props.ForFile;
  item.ForDir = props.ForDir;
  item.WildcardMatching = props.WildcardMatching;
  AddItem(include, item);
}
*/

bool CCensorNode::NeedCheckSubDirs() const
{
  FOR_VECTOR (i, IncludeItems)
  {
    const CItem &item = IncludeItems[i];
    if (item.Recursive || item.PathParts.Size() > 1)
      return true;
  }
  return false;
}

bool CCensorNode::AreThereIncludeItems() const
{
  if (IncludeItems.Size() > 0)
    return true;
  FOR_VECTOR (i, SubNodes)
    if (SubNodes[i].AreThereIncludeItems())
      return true;
  return false;
}

bool CCensorNode::CheckPathCurrent(bool include, const UStringVector &pathParts, bool isFile) const
{
  const CObjectVector<CItem> &items = include ? IncludeItems : ExcludeItems;
  FOR_VECTOR (i, items)
    if (items[i].CheckPath(pathParts, isFile))
      return true;
  return false;
}

bool CCensorNode::CheckPathVect(const UStringVector &pathParts, bool isFile, bool &include) const
{
  if (CheckPathCurrent(false, pathParts, isFile))
  {
    include = false;
    return true;
  }
  if (pathParts.Size() > 1)
  {
    int index = FindSubNode(pathParts.Front());
    if (index >= 0)
    {
      UStringVector pathParts2 = pathParts;
      pathParts2.Delete(0);
      if (SubNodes[(unsigned)index].CheckPathVect(pathParts2, isFile, include))
        return true;
    }
  }
  bool finded = CheckPathCurrent(true, pathParts, isFile);
  include = finded; // if (!finded), then (true) is allowed also
  return finded;
}

/*
bool CCensorNode::CheckPath2(bool isAltStream, const UString &path, bool isFile, bool &include) const
{
  UStringVector pathParts;
  SplitPathToParts(path, pathParts);
  if (CheckPathVect(pathParts, isFile, include))
  {
    if (!include || !isAltStream)
      return true;
  }
  if (isAltStream && !pathParts.IsEmpty())
  {
    UString &back = pathParts.Back();
    int pos = back.Find(L':');
    if (pos > 0)
    {
      back.DeleteFrom(pos);
      return CheckPathVect(pathParts, isFile, include);
    }
  }
  return false;
}

bool CCensorNode::CheckPath(bool isAltStream, const UString &path, bool isFile) const
{
  bool include;
  if (CheckPath2(isAltStream, path, isFile, include))
    return include;
  return false;
}
*/

bool CCensorNode::CheckPathToRoot_Change(bool include, UStringVector &pathParts, bool isFile) const
{
  if (CheckPathCurrent(include, pathParts, isFile))
    return true;
  if (!Parent)
    return false;
  pathParts.Insert(0, Name);
  return Parent->CheckPathToRoot_Change(include, pathParts, isFile);
}

bool CCensorNode::CheckPathToRoot(bool include, const UStringVector &pathParts, bool isFile) const
{
  if (CheckPathCurrent(include, pathParts, isFile))
    return true;
  if (!Parent)
    return false;
  UStringVector pathParts2;
  pathParts2.Add(Name);
  pathParts2 += pathParts;
  return Parent->CheckPathToRoot_Change(include, pathParts2, isFile);
}

/*
bool CCensorNode::CheckPathToRoot(bool include, const UString &path, bool isFile) const
{
  UStringVector pathParts;
  SplitPathToParts(path, pathParts);
  return CheckPathToRoot(include, pathParts, isFile);
}
*/

void CCensorNode::ExtendExclude(const CCensorNode &fromNodes)
{
  ExcludeItems += fromNodes.ExcludeItems;
  FOR_VECTOR (i, fromNodes.SubNodes)
  {
    const CCensorNode &node = fromNodes.SubNodes[i];
    Find_SubNode_Or_Add_New(node.Name).ExtendExclude(node);
  }
}

int CCensor::FindPairForPrefix(const UString &prefix) const
{
  FOR_VECTOR (i, Pairs)
    if (CompareFileNames(Pairs[i].Prefix, prefix) == 0)
      return (int)i;
  return -1;
}

#if defined(_WIN32) || defined(__DOS__)

bool IsDriveColonName(const wchar_t *s)
{
  unsigned c = s[0];
  c |= 0x20;
  c -= 'a';
  return c <= (unsigned)('z' - 'a') && s[1] == ':' && s[2] == 0;
}

unsigned GetNumPrefixParts_if_DrivePath(UStringVector &pathParts)
{
  if (pathParts.IsEmpty())
    return 0;
  
  unsigned testIndex = 0;
  if (pathParts[0].IsEmpty())
  {
    if (pathParts.Size() < 4
        || !pathParts[1].IsEmpty()
        || pathParts[2] != L"?")
      return 0;
    testIndex = 3;
  }
  if (NWildcard::IsDriveColonName(pathParts[testIndex]))
    return testIndex + 1;
  return 0;
}

#endif

static unsigned GetNumPrefixParts(const UStringVector &pathParts)
{
  if (pathParts.IsEmpty())
    return 0;

  /* empty last part could be removed already from (pathParts),
     if there was tail path separator (slash) in original full path string. */
  
  #if defined(_WIN32) || defined(__DOS__)

  if (IsDriveColonName(pathParts[0]))
    return 1;
  if (!pathParts[0].IsEmpty())
    return 0;

  if (pathParts.Size() == 1)
    return 1;
  if (!pathParts[1].IsEmpty())
    return 1;
  if (pathParts.Size() == 2)
    return 2;
  if (pathParts[2] == L".")
    return 3;

  unsigned networkParts = 2;
  if (pathParts[2] == L"?")
  {
    if (pathParts.Size() == 3)
      return 3;
    if (IsDriveColonName(pathParts[3]))
      return 4;
    if (!pathParts[3].IsEqualTo_Ascii_NoCase("UNC"))
      return 3;
    networkParts = 4;
  }

  networkParts +=
      // 2; // server/share
      1; // server
  if (pathParts.Size() <= networkParts)
    return pathParts.Size();
  return networkParts;

  #else
  
  return pathParts[0].IsEmpty() ? 1 : 0;
 
  #endif
}

void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &path,
    const CCensorPathProps &props)
{
  if (path.IsEmpty())
    throw "Empty file path";

  UStringVector pathParts;
  SplitPathToParts(path, pathParts);

  CCensorPathProps props2 = props;

  bool forFile = true;
  bool forDir = true;
  const UString &back = pathParts.Back();
  if (back.IsEmpty())
  {
    // we have tail path separator. So it's directory.
    // we delete tail path separator here even for "\" and "c:\"
    forFile = false;
    pathParts.DeleteBack();
  }
  else
  {
    if (props.MarkMode == kMark_StrictFile
        || (props.MarkMode == kMark_StrictFile_IfWildcard
            && DoesNameContainWildcard(back)))
      forDir = false;
  }

  
  UString prefix;
  
  int ignoreWildcardIndex = -1;

  // #ifdef _WIN32
  // we ignore "?" wildcard in "\\?\" prefix.
  if (pathParts.Size() >= 3
      && pathParts[0].IsEmpty()
      && pathParts[1].IsEmpty()
      && pathParts[2] == L"?")
    ignoreWildcardIndex = 2;
  // #endif

  if (pathMode != k_AbsPath)
  {
    // detection of the number of Skip Parts for prefix
    ignoreWildcardIndex = -1;

    const unsigned numPrefixParts = GetNumPrefixParts(pathParts);
    unsigned numSkipParts = numPrefixParts;

    if (pathMode != k_FullPath)
    {
      // if absolute path, then all parts before last part will be in prefix
      if (numPrefixParts != 0 && pathParts.Size() > numPrefixParts)
        numSkipParts = pathParts.Size() - 1;
    }
    {
      int dotsIndex = -1;
      for (unsigned i = numPrefixParts; i < pathParts.Size(); i++)
      {
        const UString &part = pathParts[i];
        if (part == L".." || part == L".")
          dotsIndex = (int)i;
      }

      if (dotsIndex >= 0)
      {
        if (dotsIndex == (int)pathParts.Size() - 1)
          numSkipParts = pathParts.Size();
        else
          numSkipParts = pathParts.Size() - 1;
      }
    }

    // we split (pathParts) to (prefix) and (pathParts).
    for (unsigned i = 0; i < numSkipParts; i++)
    {
      {
        const UString &front = pathParts.Front();
        // WIN32 doesn't support wildcards in file names
        if (props.WildcardMatching)
          if (i >= numPrefixParts && DoesNameContainWildcard(front))
            break;
        prefix += front;
        prefix.Add_PathSepar();
      }
      pathParts.Delete(0);
    }
  }

  int index = FindPairForPrefix(prefix);
  if (index < 0)
  {
    index = (int)Pairs.Size();
    Pairs.AddNew().Prefix = prefix;
  }

  if (pathMode != k_AbsPath)
  {
    if (pathParts.IsEmpty() || (pathParts.Size() == 1 && pathParts[0].IsEmpty()))
    {
      // we create universal item, if we skip all parts as prefix (like \ or L:\ )
      pathParts.Clear();
      pathParts.Add(UString("*"));
      forFile = true;
      forDir = true;
      props2.WildcardMatching = true;
      props2.Recursive = false;
    }
  }

  /*
  // not possible now
  if (!forDir && !forFile)
  {
    UString s ("file path was blocked for files and directories: ");
    s += path;
    throw s;
    // return; // for debug : ignore item (don't create Item)
  }
  */

  CItem item;
  item.PathParts = pathParts;
  item.ForDir = forDir;
  item.ForFile = forFile;
  item.Recursive = props2.Recursive;
  item.WildcardMatching = props2.WildcardMatching;
  Pairs[(unsigned)index].Head.AddItem(include, item, ignoreWildcardIndex);
}

/*
bool CCensor::CheckPath(bool isAltStream, const UString &path, bool isFile) const
{
  bool finded = false;
  FOR_VECTOR (i, Pairs)
  {
    bool include;
    if (Pairs[i].Head.CheckPath2(isAltStream, path, isFile, include))
    {
      if (!include)
        return false;
      finded = true;
    }
  }
  return finded;
}
*/

void CCensor::ExtendExclude()
{
  unsigned i;
  for (i = 0; i < Pairs.Size(); i++)
    if (Pairs[i].Prefix.IsEmpty())
      break;
  if (i == Pairs.Size())
    return;
  unsigned index = i;
  for (i = 0; i < Pairs.Size(); i++)
    if (index != i)
      Pairs[i].Head.ExtendExclude(Pairs[index].Head);
}

void CCensor::AddPathsToCensor(ECensorPathMode censorPathMode)
{
  FOR_VECTOR(i, CensorPaths)
  {
    const CCensorPath &cp = CensorPaths[i];
    AddItem(censorPathMode, cp.Include, cp.Path, cp.Props);
  }
  CensorPaths.Clear();
}

void CCensor::AddPreItem(bool include, const UString &path, const CCensorPathProps &props)
{
  CCensorPath &cp = CensorPaths.AddNew();
  cp.Path = path;
  cp.Include = include;
  cp.Props = props;
}

}
