/*{{{}}}*/
/*{{{  #includes*/
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#ifndef NO_GETOPT_H
#  include <getopt.h>
#endif
#include <errno.h>
#include <dirent.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>
#include <time.h>

#define BIND_CONST
#include <h/keys.h>
#include <h/envvar_str.h>
#include <h/os.h>

#include <lib/ori_add_lib.h>
#include <lib/ori_rc_lib.h>
#include <lib/ori_re_lib.h>
#include <h/rcformat.h>
/*}}}  */

/*{{{  variables*/
#define START_DEPTH 255
/*{{{  fold-marks*/
char fold_open_str[FOLD_TAG_LENGTH+1]=BEGIN_STANDARD;
char fold_close_str[FOLD_TAG_LENGTH+1]=END_STANDARD;
char fold_file_str[FOLD_TAG_LENGTH+1]=FILE_STANDARD;
char fold_line_str[FOLD_TAG_LENGTH+1]=LINE_STANDARD;
/*}}}  */
/*{{{  for commandline-options*/
char const *keybase=0;
char const *ori_path=ORIGAMI_RC_PATH;
int always=0;
int mark_no_perm=0;
#ifdef REGEXP
   char const *match_str="";
   regex_t preg_match;
   char const *no_match_str="";
   regex_t preg_no_match;
   int aborted=0;
   regmatch_t ma[1];
#endif
/*}}}  */
int single=0;
int depth=START_DEPTH;
uid_t user;
gid_t group;
int long_format=0;
/*}}}  */

/*{{{  error_exit*/
void error_exit(char const * const m)
 {
   fputs(m,stderr);
   exit(1);
 }
/*}}}  */
/*{{{  set_other_mark*/
/*{{{  parse rcfile*/
int rc_file_parser(char const * const mark_mode,FILE * const rcfile)
{
  readtags input;

  rc_get_w(rcfile);
  while ((input=rc_get_c(rcfile))!=RC_ENDE && input!=RC_BIND)
   { switch (input) {
       /*{{{  termalias*/
       case RC_TERMALIAS:
        { int ch;

          while ((ch=rc_get_c(rcfile)) && ch!=EOF)
             do ch=rc_get_c(rcfile); while (ch && ch!=EOF);
          do ch=rc_get_c(rcfile); while (ch && ch!=EOF);
          continue;
        }
       /*}}}  */
       /*{{{  usermodestrings*/
       case RC_MODE: {
         int lg;

         rc_get_c(rcfile);
         lg=rc_get_w(rcfile);
         if (lg)
            rc_skip_cw(rcfile,lg,0);
         lg=rc_get_w(rcfile);
         if (lg)
            rc_skip_cw(rcfile,lg,0);
         continue;
       }
       /*}}}  */
       /*{{{  macroload/init*/
       case RC_LOADMACRO:
       case RC_INITMACRO:
        { int lg;

          rc_get_w(rcfile);
          lg=rc_get_w(rcfile);
          rc_skip_cw(rcfile,0,lg);
          continue;
        }
       /*}}}  */
       /*{{{  consts*/
       case RC_CONST:
          rc_skip_cw
           ( rcfile,
             BIND_NAME_LEN+NO_OCL_CMD_OPTS*(2*BIND_NAME_LEN+2),
             RCC_SIZE
           );
          continue;
       /*}}}  */
       /*{{{  comment/os-extension*/
       case RC_COMMENT:
       case RC_OS_EXTENSION:
          while ((input=rc_get_c(rcfile)) && input!=EOF);
          continue;
       /*}}}  */
       /*{{{  mouse-tab*/
       case RC_M_TAB:
         rc_skip_cw(rcfile,0,rc_get_w(rcfile));
         continue;
       /*}}}  */
       /*{{{  marks*/
       case RC_MARKS:
        { char name[FOLD_PATTERN_LEN];
          int i;

          /*{{{  read stored name*/
          for (i=0;i<(FOLD_PATTERN_LEN-1);i++)
            name[i]=rc_get_c(rcfile);
          name[FOLD_PATTERN_LEN-1]='\0';
          /*}}}  */
          if (!strcmp(mark_mode,name))
           /*{{{  reset mark-strings*/
           { for (i=0;i<FOLD_TAG_LENGTH;i++) fold_open_str[i]=rc_get_c(rcfile);
             for (i=0;i<FOLD_TAG_LENGTH;i++) fold_line_str[i]=rc_get_c(rcfile);
             for (i=0;i<FOLD_TAG_LENGTH;i++) fold_file_str[i]=rc_get_c(rcfile);
             for (i=0;i<FOLD_TAG_LENGTH;i++) fold_close_str[i]=rc_get_c(rcfile);
             return(0);
           }
           /*}}}  */
          else
           /*{{{  overread the data*/
           { rc_skip_cw(rcfile,4*FOLD_TAG_LENGTH,0);
             continue;
           }
           /*}}}  */
        }
       /*}}}  */
       /*{{{  keydef,macset,set,strings,comp-dat*/
       case RC_DEFKEY:
       case RC_MAC_SET:
       case RC_CHARSET:
       case RC_STRINGS:
       case RC_REF_COMP_STR:
          break;
       /*}}}  */
       /*{{{  error!*/
       default:
          fputs("incorrect rc-data,",stderr);
          return(1);
       /*}}}  */
     }
     break;
   }
  fputs("no matching marks found, ",stderr);
  return(1);
}
/*}}}  */

int set_other_mark(char const * const mark_mode)
{
  char const *keyfile;
  char base[_POSIX_PATH_MAX];
  int ok;
  FILE *rcfile;

  /*{{{  get base for rc file*/
  if (keybase)
     strcpy(base,keybase);
  else
     sprintf(base,BASENAMEF,"origami");
  /*}}}  */
  /*{{{  search for file in path list*/
  keyfile=get_system_filename
           ( ori_path,
             base,
             RC_ENDING,
             (char*)0
           );
  /*}}}  */
  if (!keyfile[0] || !(rcfile=fopen(keyfile,"r")))
   { fputs("no "RC_ENDING"-file found,",stderr);
     ok=1;
   }
  else
   { ok=rc_file_parser(mark_mode,rcfile);
     fclose(rcfile);
   }

  return (ok);
}
/*}}}  */
/*{{{  show_modes*/
char *show_modes(struct stat const * const f)
{ static char coding[256];
  char *s=coding;

  /*{{{  decode filetype*/
  /*{{{  dir*/
  if (S_ISDIR(f->st_mode))
     *s++='d';
  /*}}}  */
  /*{{{  block special*/
#  ifdef S_ISBLK
  else if (S_ISBLK(f->st_mode))
     *s++='b';
#  endif
  /*}}}  */
  /*{{{  character special*/
#  ifdef S_ISCHR
  else if (S_ISCHR(f->st_mode))
     *s++='c';
#  endif
  /*}}}  */
  /*{{{  symbolic link*/
#  ifdef S_ISLNK
  else if (S_ISLNK(f->st_mode))
     *s++='l';
#  endif
  /*}}}  */
  /*{{{  fifo*/
#  ifdef S_ISFIFO
  else if (S_ISFIFO(f->st_mode))
     *s++='p';
#  endif
  /*}}}  */
  /*{{{  network special*/
#  ifdef S_ISNWK
  else if (S_ISNWK(f->st_mode))
     *s++='n';
#  endif
  /*}}}  */
  /*{{{  socket*/
#  ifdef S_ISSOCK
  else if (S_ISSOCK(f->st_mode))
     *s++='s';
#  endif
  /*}}}  */
  /*{{{  file*/
  else if (S_ISREG(f->st_mode))
     *s++='-';
  /*}}}  */
  /*{{{  default unknown*/
  else
     *s++='?';
  /*}}}  */
  /*}}}  */
  /*{{{  decode owner/group/others permissions*/
  /*{{{  owner*/
#  ifdef S_IRUSR
     /*{{{  read access permission for owner*/
     if (S_IRUSR&f->st_mode)
        *s++='r';
     else
        *s++='-';
     /*}}}  */
#  endif
#  ifdef S_IWUSR
     /*{{{  write access permission for owner*/
     if (S_IWUSR&f->st_mode)
        *s++='w';
     else
        *s++='-';
     /*}}}  */
#  endif
#  ifdef S_IXUSR
     /*{{{  execute/search access permission for owner*/
     if (S_IXUSR&f->st_mode)
        *s++='x';
     else
        *s++='-';
     /*}}}  */
#  endif
  /*}}}  */
  /*{{{  group*/
#  ifdef S_IRGRP
     /*{{{  read access permission for group*/
     if (S_IRGRP&f->st_mode)
        *s++='r';
     else
        *s++='-';
     /*}}}  */
#  endif
#  ifdef S_IWGRP
     /*{{{  write access permission for group*/
     if (S_IWGRP&f->st_mode)
        *s++='w';
     else
        *s++='-';
     /*}}}  */
#  endif
#  ifdef S_IXGRP
     /*{{{  execute/search access permission for group*/
     if (S_IXGRP&f->st_mode)
        *s++='x';
     else
        *s++='-';
     /*}}}  */
#  endif
  /*}}}  */
  /*{{{  others*/
#  ifdef S_IROTH
     /*{{{  read access permission for others*/
     if (S_IROTH&f->st_mode)
        *s++='r';
     else
        *s++='-';
     /*}}}  */
#  endif
#  ifdef S_IWOTH
     /*{{{  write access permission for others*/
     if (S_IWOTH&f->st_mode)
        *s++='w';
     else
        *s++='-';
     /*}}}  */
#  endif
#  ifdef S_IXOTH
     /*{{{  execute/search access permission for others*/
     if (S_IXOTH&f->st_mode)
        *s++='x';
     else
        *s++='-';
     /*}}}  */
#  endif
  /*}}}  */
  /*}}}  */
# ifndef NO_POSIX_STAT
     /*{{{  number of bytes and time*/
     { struct passwd const *x=getpwuid(f->st_uid);
       char t[27];

       strcpy(t,ctime(&f->st_mtime));
       t[16]='\0';
       sprintf(s," %8s%9ld %s",x?x->pw_name:"",(long)f->st_size,t+4);
       while (*s) s++;
     }
     /*}}}  */
# else
     NO_POSIX_STAT
# endif
  *s++=' ';
  *s='\0';
  return(coding);
}
/*}}}  */
/*{{{  path_get*/
void path_get
 ( int const absolut,
   char * const dir,
   char const * const cur,
   char const * const ent
 )
 {
   *dir='\0';
   if (!absolut || !getcwd(dir,_POSIX_PATH_MAX))
    { if (!*ent || !IS_ROOT(ent))
         strcpy(dir,cur);
      else
         *dir='\0';
      if (*dir && *ent)
         strcat(dir,PATH_SEP);
      strcat(dir,ent);
      if (absolut)
         printf("getcwd(%s): %s\n",dir,strerror(errno));
    }
 }
/*}}}  */
/*{{{  dirhandle*/
/*{{{  dir_ent_cmp*/
int dir_ent_cmp(const void *a, const void *b)
{
  return(strcmp(*((char**)a),*((char**)b)));
}
/*}}}  */
/*{{{  forward for filehandle*/
void filehandle
 ( int,
   char const * const name,
   char const * const dir,
   int const curr_depth
 );
/*}}}  */

void dirhandle
 ( int absolut,
   char const * const name,
   char const * const dir,
   int const curr_depth,
   struct stat const * const buff
 )
{
  if (curr_depth<=depth)
   /*{{{  recursion*/
   { char fn[_POSIX_PATH_MAX+1];
     DIR *dirptr;

     if (absolut)
      /*{{{  chdir and read*/
      { if (chdir(name))
           absolut=0;
        else
           strcpy(fn,CURR_DIR);
      }
      /*}}}  */
     if (!absolut)
      /*{{{  relative, don't change dir, combine pathname for dir*/
      { if (!IS_ROOT(name))
         { strcpy(fn,dir);
           if (fn[0]) strcat(fn,PATH_SEP);
         } 
        else
           fn[0]=0;
        strcat(fn,name);
      } 
      /*}}}  */
     if (fn[0])
       if ((dirptr=opendir(fn))) {
         /*{{{  can read the entries*/
         /*{{{  local variables*/
         char new_wd[_POSIX_PATH_MAX+1];
         struct dirent *ls;
         char **s;
#         define bl_size 128
         int count=0;
         int curr;
         int curr_size=bl_size;
         int marked_point=0;
         /*}}}  */

         /*{{{  get buffer for dir-entries*/
         if (!(s=ORImalloc(curr_size*sizeof(char*))))
            error_exit("no memory\n");
         /*}}}  */
         /*{{{  read the contents of the directory*/
         while ((ls=readdir(dirptr))) {
           /*{{{  expand entry-list*/
           if (count==curr_size) {
             char **old=s;
             int x=0;

             if (!(s=ORImalloc((curr_size+bl_size)*sizeof(char*))))
                error_exit("no memory\n");
             while (x<curr_size) {s[x]=old[x];x++;}
             ORIfree(old);
             curr_size+=bl_size;
           }
           /*}}}  */
           /*{{{  get memory for entry*/
           if (!(s[count]=ORImalloc(strlen(ls->d_name)+1)))
              error_exit("no memory\n");
           /*}}}  */
           strcpy(s[count++],ls->d_name);
         }
         /*}}}  */
         qsort((void*)s,(size_t)count,(size_t)sizeof(char*),dir_ent_cmp);
         if (!single)
            printf("%s  %s%s\n",
                   fold_open_str,long_format?show_modes(buff):"",name);
         /*{{{  get new working directory*/
         path_get(absolut,new_wd,dir,name);
         /*}}}  */
         /*{{{  handle all entries of the directory*/
         curr=0;
         while (curr<count) {
           if (strcmp(CURR_DIR,s[curr]) && (single || strcmp(PREV_DIR,s[curr]))) {
             /*{{{  maybe mark .???*/
             if (!marked_point && s[curr][0]=='.') {
               marked_point=1;
               printf("%s  .xxx files\n",fold_open_str);
             }
             /*}}}  */
             /*{{{  maybe endmark .???*/
             if (marked_point && s[curr][0]!='.') {
               marked_point=0;
               printf("%s\n",fold_close_str);
             }
             /*}}}  */
             filehandle(absolut,s[curr],new_wd,curr_depth);
             ORIfree(s[curr]);
           }
           curr++;
         }
         /*}}}  */
         if (marked_point) printf("%s\n",fold_close_str);
         if (!single) printf("%s\n",fold_close_str);
         ORIfree(s);
         closedir(dirptr);
         /*}}}  */
       } else
         printf("cannot read this directory %s.\n",fn);
     else
       printf("%s cannot enter\n",name);
     if (absolut) chdir(dir);
   }
   /*}}}  */
  else
    if (single)
      printf("%sF %s%s\n%sF %s%s%s\n%s\n",
             fold_open_str,long_format?show_modes(buff):"",name,
             fold_file_str,dir,PATH_SEP,name,
             fold_close_str);
    else
      printf("maxdepth reached for %s.\n",name);
}
/*}}}  */
/*{{{  filehandle*/
void filehandle
 ( int absolut,
   char const * const name,
   char const * const dir,
   int const curr_depth
 )
{

# ifdef REGEXP
     if (curr_depth)
        if
         (    (*match_str && regexec(&preg_match,name,(size_t)0,ma,0)!=0)
           || (*no_match_str && regexec(&preg_no_match,name,(size_t)0,ma,0)==0)
         )
           return;
# endif
  if (curr_depth<=depth)
   { struct stat buff;
     char fn[_POSIX_PATH_MAX+1];

     /*{{{  get filename for stat*/
     if (absolut)
        strcpy(fn,name);
     else
      { if (!IS_ROOT(name))
         { strcpy(fn,dir);
           if (fn[0]) strcat(fn,PATH_SEP);
         } 
        else
           fn[0]='\0';
        strcat(fn,name);
      }
     /*}}}  */
     if (stat(fn,&buff))
       printf("cannot stat %s=%s: %s.\n",name,name,strerror(errno));
     else {
       if (   ((buff.st_uid==user) && (S_IRUSR&buff.st_mode))
           || ((buff.st_gid==group) && (S_IRGRP&buff.st_mode))
           || (S_IROTH&buff.st_mode)
           || OS_READPERM(fn,&buff)
           || always )
       {
         /*{{{  handle a directory*/
         if (S_ISDIR(buff.st_mode))
            dirhandle(absolut,name,dir,curr_depth+1,&buff);
         /*}}}  */
         /*{{{  handle a file*/
         if (S_ISREG(buff.st_mode)) {
           printf("%sF %s%s\n",
                  fold_open_str,
                  long_format?show_modes(&buff):"",
                  name);
           printf("%sF %s%s%s\n",fold_file_str,dir,PATH_SEP,name);
           printf("%s\n",fold_close_str);
         }
         /*}}}  */
       } else if (mark_no_perm)
         printf("no permission for %s\n",name);
     }
   }
}
/*}}}  */

/*{{{  main*/
int main(int argc, char *argv[])
{ char call_path[_POSIX_PATH_MAX+1];
  int absolut=1;
  int no;

  user=geteuid();
  group=getegid();
  /*{{{  parse arguments*/
  { int c;
    char const *mark_mode=0;

    while ((c=getopt(argc,argv,"k:lm:hd:apM:srf:F:R:"))!=EOF)
    switch (c)
    {
      /*{{{  f*/
      case 'f':
#        ifdef REGEXP
            match_str=optarg;
#        endif
         break;
      /*}}}  */
      /*{{{  F*/
      case 'F':
#        ifdef REGEXP
            no_match_str=optarg;
#        endif
         break;
      /*}}}  */
      /*{{{  d*/
      case 'd':
        if (single) d_s_error:
           error_exit("d and s conflict\n");
        depth=atoi(optarg);
        if (depth<=0)
         { puts("nothing to do.");
           exit(0);
         }
        break;
      /*}}}  */
      /*{{{  l*/
      case 'l':
        long_format=1;
        break;
      /*}}}  */
      /*{{{  r*/
      case 'r': absolut=0;break;
      /*}}}  */
      /*{{{  s*/
      case 's':
         if (depth!=START_DEPTH)
            goto d_s_error;
         single=1;
         depth=1;
         break;
      /*}}}  */
      /*{{{  m*/
      case 'm': mark_mode=optarg;break;
      /*}}}  */
      /*{{{  a*/
      case 'a': always=1;break;
      /*}}}  */
      /*{{{  p*/
      case 'p': mark_no_perm=1;break;
      /*}}}  */
      /*{{{  k*/
      case 'k': keybase=optarg;break;
      /*}}}  */
      /*{{{  M*/
      case 'M':
       { char *s=optarg;
         int k;

         for (k=0;k<FOLD_TAG_LENGTH;)
            if (!(fold_open_str[k++]= *s++)) M_error:
               error_exit("invalid marks given\n");
         fold_open_str[FOLD_TAG_LENGTH]='\0';
         for (k=0;k<FOLD_TAG_LENGTH;)
            if (!(fold_file_str[k++]= *s++))
               goto M_error;
         fold_file_str[FOLD_TAG_LENGTH]='\0';
         for (k=0;k<FOLD_TAG_LENGTH;)
            if (!(fold_line_str[k++]= *s++))
               goto M_error;
         fold_line_str[FOLD_TAG_LENGTH]='\0';
         for (k=0;k<FOLD_TAG_LENGTH;)
            if (!(fold_close_str[k++]= *s++))
               goto M_error;
         fold_close_str[FOLD_TAG_LENGTH]='\0';
         if (*s)
            goto M_error;
         break;
       }
      /*}}}  */
      /*{{{  R*/
      case 'R':
         ori_path=optarg;
         break;
      /*}}}  */
      /*{{{  h and error*/
      default:
      case '?':
        fputs("dirfold: invalid option\n",stderr);
      case 'h':
        fputs
         ( "Usage:\tdirfold [-ahlpsr] [-d depth]"
#          ifdef REGEXP
              " [-f match]"
#          endif
           " [-k namebase]\n\t[-m markname]"
#          ifdef REGEXP
              " [-F nomatch]"
#          endif
           " [-M markpatterns] [-R "RC_ENDING"-path-list]\n\t[filename] ...\n",
           stderr
         );
        exit((c=='h')?0:1);
      /*}}}  */
    }
#   ifdef REGEXP
    /*{{{  handle [no_]match string*/
      if (*match_str)
       /*{{{  compile*/
       { if
          (    regcomp
                ( &preg_match,
                  match_str,
                  USE_NOSUB(0)|REG_EXTENDED|USE_SUB_LIMIT(0,preg_match)
                )
            != 0
          )
            error_exit("dirfold: invalid match expression\n");
       }
       /*}}}  */
      if (*no_match_str)
       /*{{{  compile*/
       { if
          (    regcomp
                ( &preg_no_match,
                  no_match_str,
                  USE_NOSUB(0)|REG_EXTENDED|USE_SUB_LIMIT(0,preg_match)
                )
            != 0
          )
            error_exit("dirfold: invalid no-match expression\n");
       }
       /*}}}  */
    /*}}}  */
#   endif
    /*{{{  no marks found*/
    if (mark_mode && set_other_mark(mark_mode))
       error_exit("dirfold: mark conflict\n");
    /*}}}  */
  }
  /*}}}  */
  path_get(absolut,call_path,"","");
  if (argv[no=optind])
     do
        filehandle(absolut,argv[no],call_path,0);
     while (argv[++no]);
  else
     filehandle(absolut,CURR_DIR,call_path,0);
  if (call_path[0])
     chdir(call_path);
  return(0);
}
/*}}}  */
