#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "cmos.h"
#include "pattern.h"
#include "OSLib/osfile.h"
#include "OSLib/osfscontro.h"
#include "OSLib/osgbpb.h"

#ifndef NDEBUG
#include <stdarg.h>
#include <stdio.h>
#include "OSLib/wimp.h"
#endif

#define WMANY '*'
#define WSINGLE '#'
#define WPART '%'
#define DIRSEP '.'
#define ESCPAT '\\'

static char *patterns = 0;
static int patterns_size;

const os_error nomem_error = {0, "Not enough memory"};
const os_error nofile_error = {0, "File not found or is a directory"};
const os_error syntax_error = {0,
  "*SmartOpenDir <full dirname> [<x> <y> [<width> <height>]] [<switches>]"};
/*static const os_error pattern_error = {0, "Bad pattern file"};*/

#ifndef NDEBUG
static void report(const char *report, ...)
{
  os_error err;
  va_list va;

  va_start(va, report);
  vsprintf(err.errmess, report, va);
  va_end(va);
  xwimp_report_error(&err, 1, "SmartOpenDir", 0);
}
#endif

os_error const *load_patterns(char const *filename)
{
  int i;
  os_error *e = xosfile_read_stamped(filename, &i, 0, 0,
        &patterns_size, 0, 0);
  if (e) return e;
  if (i != 1)
    return &nofile_error;

  patterns = realloc(patterns, patterns_size + 1);
  if (!patterns)
    return &nomem_error;
  e = xosfile_load_stamped(filename, (byte *) patterns,
        0, 0, 0, 0, 0);
  if (e) return e;

  /* Change patterns to make it easier to process */
  for (i = 0; i < patterns_size; ++i)
  {
    if (iscntrl(patterns[i]))
    {
      if (patterns[i] == '\t')
        patterns[i] = ' ';
      else
        patterns[i] = 0;
    }
    else if (patterns[i] == ESCPAT)
    {
      ++i;
    }
    else if (patterns[i] == WMANY)
    {
      /*
      int j;
      int oddesc = 0;

      for (j = i - 1; j >= 0 && patterns[j] == ESCPAT; --j)
        oddesc = !oddesc;
      if (!oddesc)
      {
      */
        switch (patterns[i + 1])
        {
          case WSINGLE:
            patterns[i] = WSINGLE;
            patterns[i + 1] = WMANY;
            break;
          case WMANY:
          case WPART:
            memmove(patterns + i + 1, patterns + i + 2,
                    --patterns_size - i - 1);
            --i;
            break;
          default:
            break;
        }
     /* } */
    }
    else
      patterns[i] = tolower(patterns[i]);
  }
  if (patterns[patterns_size - 1] != 0)
    patterns[patterns_size++] = 0;

  return 0;
}

void free_patterns()
{
  free(patterns);
  patterns = 0;
}

bool match_pattern(char const *directory, char const *pattern)
{
  char canon[256];      /* Canonicalised directory name */
  int i, j;
  int lmi = -1, lmj = -1;       /* Last place where i & j matched for * */

  if (xosfscontrol_canonicalise_path(directory, canon, 0, 0, sizeof(canon), 0))
    return FALSE;
#ifndef NDEBUG
  report("Matching directory \"%s\" against pattern \"%s\"\n", canon, pattern);
#endif
  for (i = 0, j = 0; ; )
  {
    switch (pattern[j])
    {
      case WMANY:
        lmj = j++;
        if (pattern[j] == ESCPAT)
          ++j;
        if (pattern[j] <= ' ')
          return TRUE;
        while (tolower(canon[i]) != pattern[j] && canon[i] > ' ')
          ++i;
        if (canon[i] <= ' ')
          return FALSE;
        lmi = i;
        break;
      case WPART:
        while (canon[i] != DIRSEP && canon[i] > ' ')
          ++i;
        ++j;
        if (canon[i] <= ' ' && pattern[j] <= ' ')
          return TRUE;
        break;
      case WSINGLE:
        ++j;
        ++i;
        break;
      case ESCPAT:
        ++j;
      default:
        if (canon[i] <= ' ' && pattern[j] <= ' ')
          return TRUE;
        if (tolower(canon[i]) != pattern[j])
        {
          if (lmi == -1)
            return FALSE;
          else
          {
            i = ++lmi;
            j = lmj;
            break;
          }
        }
        ++i;
        ++j;
    }
  }
  return FALSE;
}

/* Find directory name in command *tail* */
static os_error const *find_directory(char const *command,
        char const **directory)
{
  *directory = strstr(command, "-directory ");
  if (*directory)
    *directory += 11;
  else
  {
    *directory = strstr(command, "-dir ");
    if (*directory)
      *directory += 5;
  }
  if (!*directory)
  {
    *directory = command;
    if (*command == '-')
      return &syntax_error;
  }
  return 0;
}

/* Find effective start of current line */
static int line_start(int i)
{
  while (i < patterns_size)
  {
    /* Find end of current line */
    /* Skip all blank lines */
    while (i < patterns_size && !patterns[i]) ++i;
    if (i == patterns_size)
      return i;
    /* Skip comment lines beginning with whitespace or " */
    if (isspace(patterns[i]) || patterns[i] == '\"')
    {
      while (i < patterns_size && patterns[i++]);
      /* Back round loop in case next line is also blank/comment */
      continue;
    }
  #ifndef NDEBUG
    report("Pattern line: \"%s\"\n", &patterns[i]);
  #endif
    return i;
  }
  return i;
}

static int next_line(int i)
{
  /* Find end of current line */
  while (i < patterns_size && patterns[i++]);
  return i;
}

static int ext_cmos;

int match_patterns(char const *directory)
{
  int i;
  int cmos;

  if (!patterns)
    return -1;
  for (i = 0; i < patterns_size; )
  {
    i = line_start(i);
    if (match_pattern(directory, patterns + i))
    {
      /* Get cmos options from pattern */

      /* Find end of pattern */
      while (patterns[i] && !isspace(patterns[i]))
        ++i;
      if (!patterns[i])
        return -1;
      /* Skip spaces and tabs */
      while (patterns[i] && isspace(patterns[i]))
        ++i;
      if (!patterns[i] || !isxdigit(patterns[i]) || !isxdigit(patterns[i+1]) ||
        (patterns[i+2] && !isspace(patterns[i+2]) && !isxdigit(patterns[i+2])))
        return -1;
      if (strlen(&patterns[i]) <= 7)
        sscanf(&patterns[i], "%x", &cmos);
      else
      {
        int action;
        sscanf(&patterns[i], "%1x%2x%6x", &action, &ext_cmos, &cmos);
        cmos |= action << 24;
      }
      return cmos;
    }
    i = next_line(i);
  }
  return -1;
}

#define SLSIZE 256

/* Simple test for whether a directory contains more than n entries */
/*
static bool dir_more_simple(const char *directory, int n)
{
  bool result;
  char *sl;

  sl = malloc(SLSIZE);
  if (!sl)
    return FALSE;
  result = (!xosgbpb_dir_entries(directory, (osgbpb_string_list *) sl, 1,
      n, SLSIZE, 0, &n, 0) && n);
  free(sl);
  return result;
}
*/

static bool dir_more_complex(const char *directory, int n)
{
  int count;
  int total;
  int index;
  char *sl;

  sl = malloc(SLSIZE + 256);
  if (!sl)
    return FALSE;
  for (total = index = 0; index != -1 && total <= n; )
  {
    if (xosgbpb_dir_entries(directory, (osgbpb_string_list *) sl, 64,
      index, SLSIZE, 0, &count, &index))
    {
      free(sl);
      return FALSE;
    }
    total += count;
  }
  free(sl);
  return total > n;
}

static bool dir_has_long_name(const char *directory, int n)
{
  int count;
  int longest = 0;
  int index;
  char *sl;

  sl = malloc(256);
  if (!sl)
    return FALSE;
  for (index = 0; index != -1; )
  {
    if (xosgbpb_dir_entries(directory, (osgbpb_string_list *) sl, 1,
      index, 256, 0, &count, &index))
    {
      free(sl);
      return FALSE;
    }
    if (count > 0)
    {
      int len = strlen(sl);
      if (len > longest)
      {
        longest = len;
        if (len > n)
          break;
      }
    }
  }
  free(sl);
  return longest > n;
}

bool match_and_set(char const *directory)
{
  bool more_files, long_name;
  bool cond_met = FALSE;
  int cmos = match_patterns(directory);
  int action = (cmos & 0xf000000) >> 24;
#ifndef NDEBUG
  report("cmos = 0x%x\n", cmos);
#endif
  if (cmos == -1)
    return FALSE;
  else if (cmos >= 0x1000000)
  {
    switch (action)
    {
      case 1:
        cond_met = dir_more_complex(directory, (cmos & 0xff0000) >> 16);
      #ifndef NDEBUG
        report("N files > %d ?: %d\n", (cmos & 0xff0000) >> 16, cond_met);
      #endif
        break;
      case 2:
        cond_met = dir_has_long_name(directory, (cmos & 0xff0000) >> 16);
      #ifndef NDEBUG
        report("Long name > %d ?: %d\n", (cmos & 0xff0000) >> 16, cond_met);
      #endif
        break;
      case 3: case 4: case 5:
        cond_met = more_files = dir_more_complex(directory,
                                                 (cmos & 0xff0000) >> 16);
      #ifndef NDEBUG
        report("N files > %d ?: %d\n", (cmos & 0xff0000) >> 16, more_files);
      #endif
        if (cond_met && action==3   ||   !cond_met && action==4) break;
        cond_met = long_name = dir_has_long_name(directory, ext_cmos);
      #ifndef NDEBUG
        report("Long name > %d ?: %d\n", ext_cmos, long_name);
      #endif
        if (action==5) cond_met = more_files ^ long_name;
        break;
      default:
      #ifndef NDEBUG
        report("Invalid extended action %d\n", action);
      #endif
        return FALSE;
    }
    if (cond_met)
    {
    #ifndef NDEBUG
      report("Conditions met, setting cmos to 0x%x\n", cmos & 0xff);
    #endif
      cmos &= 0xff;
    }
    else
    {
    #ifndef NDEBUG
      report("Conditions not met, setting cmos to 0x%x\n",
             (cmos & 0xff00) >> 8);
    #endif
      cmos = (cmos & 0xff00) >> 8;
    }
  }
  cmos_set(cmos);
  return TRUE;
}

static const char FilerOpenDir[] = "%Filer_OpenDir ";
static const int lfod = sizeof(FilerOpenDir) - 1;

/* Convert original command tail to "%Filer_OpenDir ... " */
os_error const *convert_command(char const *command, char **newcom)
{
  int i;
  int cmos;
  char const *dir;
  os_error const *e;
  static char com[256];

#ifndef NDEBUG
  report("original command=\n\"%s\"\n", command);
#endif
  strcpy(com, FilerOpenDir);

  while (isspace(*command)) ++command;
  if (iscntrl(*command))
  {
    *newcom = 0;
    return &syntax_error;
  }

  /* Make command zero-terminated */
  for (i = 0; !iscntrl(command[i]) || command[i] == '\t'; ++i)
  {
    if (command[i] == '\t')
      com[i + lfod] = ' ';
    else
      com[i + lfod] = command[i];
  }
  com[i + lfod] = 0;
#ifndef NDEBUG
  report("lfod = %d, command=\n\"%s\"\n", lfod, com);
#endif

  *newcom = com;
  e = find_directory(command, &dir);
  if (e)
    return e;
  cmos = match_patterns(dir);
  if (cmos != -1)
    cmos_set(cmos);
  return 0;
}
