#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>

static int all_entries = 0;
static int all_entries_except_dotdot = 0;
static int file_type_decorations = 0;
static int columnar_output = 0;
static int directory_searching = 1;
static int show_inum = 0;

void print_in_columns (char *str, int *len) {
  static int columns = 3;
  static int column_width = 25;
  int l = *len;
  l += printf ("%s", str);
  if (!columnar_output)
    columns = 1;
  if (l > ((columns - 1)*column_width)) {
    l = 0;
    putchar('\n');
  } else {
    while (l % column_width != 0) {
      ++l;
      putchar(' ');
    }
  }
  *len = l;
}

char *file_display_name (char *dir, char *fname) {
  char namebuf[1024];
  static char buf[1024];
  struct stat st;
  if (!(file_type_decorations || show_inum))
    return fname;
  if (dir) {
    sprintf (namebuf, "%s/%s", dir, fname);
  } else {
    strcpy (namebuf, fname);
  }
  if (stat (namebuf, &st) == 0) {
    int m = st.st_mode;
    if (show_inum) {
      sprintf (buf, "%u %s", st.st_ino, fname);
    } else {
      strcpy (buf, fname);
    }
    if (file_type_decorations) {
      if (S_ISDIR(m)) strcat (buf, "/");
      else if (S_ISLNK(m)) strcat (buf, "@");
      else if (S_ISSOCK(m)) strcat (buf, "=");
      else if (S_ISFIFO(m)) strcat (buf, "|");
      else if (S_ISREG(m) && (m & (S_IXUSR|S_IXGRP|S_IXOTH))) strcat (buf, "*");
    }
    return buf;
  } else {
    return fname;
  }
}

void list_directory (char *dirname, int show_name) {
  DIR *dir = opendir (dirname);
  struct dirent *de;
  int len = 0;

  if (!directory_searching) {
    print_in_columns (file_display_name (NULL, dirname), &len);
  } else {
    if (show_name)
      printf ("%s:\n", dirname);
    while ((de = readdir (dir)) != 0) {
      if (de->d_name[0] == '.' && !(all_entries || all_entries_except_dotdot))
        continue;
      if ((strcmp(de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0)
          && !all_entries)
        continue;
      print_in_columns (file_display_name (dirname, de->d_name), &len);
    }
  }
  if (len > 0) putchar ('\n');

  closedir (dir);
}

int is_directory (char *name) {
  struct stat st;
  return (stat (name, &st) == 0 && S_ISDIR(st.st_mode));
}

void list_argv (int argc, char **argv) {
  int j, len = 0, first = 0;

  for (j = 0; j < argc; ++j)
    if (!is_directory (argv[j]))
      print_in_columns (file_display_name (NULL, argv[j]), &len);
  if (len > 0) putchar ('\n');

  for (j = 0; j < argc; ++j)
    if (is_directory (argv[j])) {
      if (first++) { putchar ('\n'); }
      list_directory (argv[j], 1);
    }
}

int main (int argc, char **argv) {
  int i, j;

  if (isatty(fileno(stdout)))
    columnar_output = 1;

  for (i = 1; i < argc && argv[i][0] == '-'; ++i)
    for (j = 1; j < strlen (argv[i]); ++j)
      switch (argv[i][j]) {
        case '1': columnar_output = 0; break;
        case 'A': all_entries_except_dotdot = 1; break;
        case 'C': columnar_output = 1; break;
        case 'F': file_type_decorations = 1; break;
        case 'a': all_entries = 1; break;
        case 'd': directory_searching = 0; break;
        case 'i': show_inum = 1; break;
        default:
          fprintf (stderr, "%s: unknown option -%c\n", argv[0], argv[i][j]);
          return 1;
      }

  if (i == argc)
    list_directory (".", 0);
  else if (i == argc - 1 && is_directory (argv[i]))
    list_directory (argv[i], 0);
  else
    list_argv(argc - i, argv + i);
  return 0;
}
