/* 'checkit_tiff' is a conformance checker for baseline TIFFs
 *
 * author: Andreas Romeyke, 2015-2022
 * licensed under conditions of libtiff
 * (see http://libtiff.maptools.org/misc.html)
 *
 */


#include "config_parser.h"
#include "check_helper.h"
#include "check_renderer.h"
#include "strcopy.h"
#include <assert.h>
#include <dirent.h>
#include <ctype.h>
#include <stdio.h>
//#include <bits/getopt_core.h>
#ifdef __unix__
      #include <sys/stat.h>
#else
      /* #include <sys\stat.h> */
      #include <sys/stat.h>
#endif
#ifdef HAVE_IccProfLib
#include "IccProfLibVer.h"
#endif


#define STRINGIZE(x) #x


int validate_dir(int are_valid, int flag_use_memorymapped_io, int flag_scan_recursively, const char *cfg_file,
                 const char *tiff_file_or_dir);

static void print_if_defined(FILE * filehandle, const char * s) {
    if (NULL != s && s[0] != '\0') {
        fprintf(filehandle, " %s", s);
    }
}



static int check_specific_tiff_file( const char * tiff_file, int use_memmapped) {
  GET_EMPTY_RET(res)
  /* init render pipeline */
  retmsg_t * render_pipeline = malloc( sizeof( retmsg_t) );
  if (NULL == render_pipeline) {
    exit (could_not_allocate_memory);
  }
  retmsg_t * actual_render = render_pipeline;
  actual_render->rm_type = rm_file;
  actual_render->next=NULL;
  assert(NULL != tiff_file);
  actual_render->rm_msg = str( tiff_file);
  /* parse TIFF file */
  ctiff_t * ctif = initialize_ctif( tiff_file, use_memmapped?is_memmap:is_filep );
  res = parse_header_and_endianess( ctif );
  if (res.returncode != is_valid) {
      add_to_render_pipeline(&actual_render, res.value_found, rm_hard_error);
      add_to_render_pipeline(&actual_render, empty_str(), rm_endtiff);
    //printf("res.val='%s'\n", res.value_found);
	  goto renderer_exit;
  }
  uint32 offset;
  res=get_first_IFD(ctif, &offset);
  if (res.returncode != is_valid) {
      add_to_render_pipeline(&actual_render, res.value_found, rm_hard_error);
      add_to_render_pipeline(&actual_render, empty_str(), rm_endtiff);
	  goto renderer_exit;
  }
  execute_plan(ctif);
  res = print_plan_results( actual_render);
renderer_exit:
  {
    stringbuf_t render_result_string = renderer( render_pipeline );
    for (size_t i=0; i<render_result_string.pos; i++) {
        if (strlen_of_str(render_result_string.strings[i])>0) {
            printf("%s", cstr_of_str(render_result_string.strings[i]));
        }
    }
    printf("\n");
    /* free all entries of render pipeline */
    clean_render_pipeline(&render_pipeline);
      clean_stringbuf( &render_result_string);
  }
  free_ctif( ctif );
  res.value_found=empty_str();
  return res.returncode;
}

static void print_program_header (FILE * filehandle, const char * programname) {
    fprintf( filehandle, "'%s' version: %s\n", programname, VERSION);
    fprintf( filehandle, "\trevision: %s\n", REPO_REVISION);
    fprintf( filehandle, "licensed under conditions of libtiff (see http://libtiff.maptools.org/misc.html)\n");
#ifdef HAVE_IccProfLib
    fprintf( filehandle, "(enabled full ICC profile checking using IccProfLib by color.org, version " ICCPROFLIBVER "\n");
    fprintf( filehandle, " the ICCProflib is licensed under ICC Software License, version 0.2\n");
    fprintf( filehandle, " copyright (c) 2003 The International Color Consortium. All rights reserved.)\n");
#else
    fprintf( filehandle, "(disabled full ICC profile checking, but using rudimentary ICC profile checks)\n");
#endif
    fprintf( filehandle, "(compiled with flags: ");
#ifdef ALLOW_CMMTYPE_LINO
    print_if_defined( filehandle, STRINGIZE(ALLOW_CMMTYPE_LINO));
#endif
#ifdef ALLOW_ICC1_2001_04
    print_if_defined( filehandle,  STRINGIZE(ALLOW_ICC1_2001_04));
#endif
#ifdef COMPAT_NEED_REALLOCARRAY
    print_if_defined( filehandle, STRINGIZE(COMPAT_NEED_REALLOCARRAY));
#endif
#ifdef DEBUG
    print_if_defined( filehandle, STRINGIZE(DEBUG));
#endif
#ifdef HARDEN
    print_if_defined( filehandle, STRINGIZE(HARDEN));
#endif
#ifdef HAVE_STRNDUP
    print_if_defined( filehandle, STRINGIZE(HAVE_STRNDUP));
#endif
#ifdef PCRE2_STATIC
    print_if_defined( filehandle, STRINGIZE(PCRE2_STATIC));
#endif
#ifdef WITH_STRONG_ICC
    print_if_defined( filehandle, STRINGIZE(WITH_STRONG_ICC));
#endif
#ifdef HAVE_IccProfLib
    print_if_defined( filehandle, STRINGIZE(HAVE_IccProfLib));
#endif
#ifdef HAVE_MMAP
    print_if_defined( filehandle, STRINGIZE(HAVE_MMAP));
#endif
#ifdef HAVE_MUNMAP
    print_if_defined( filehandle, STRINGIZE(HAVE_MUNMAP));
#endif
#ifdef __WIN32__
    print_if_defined( filehandle, STRINGIZE(__WIN32__));
#endif
    fprintf( filehandle, ")\n");
}

/** help function */
static void help (FILE * filehandle, const char * programname) {
    print_program_header(filehandle, programname);
    printf ("call it with:\n");
    printf ("\tcheckit_tiff [[-c|-t]|-h|-m|-d|-D|-q] <configfile> <tifffile> [<tifffile> ...]\n");
    printf ("\nwhere <tifffile> is the tiff file (or directory) to be validated\n");
    printf ("and <configfile> is the file name of the validation profile\n");
    printf ("\t-h this help\n");
    printf ("\t-c colorized output using ANSI escape sequences\n");
    printf ("\t-m uses memmapped I/O (faster validation, but needs more RAM. Not available on all operating systems.)\n");
    printf ("\t-d check all files in that directory (non-recursively)\n");
    printf ("\t-D check all files in that directory (recursively)\n");
    printf ("\t-q suppresses the output of all valid tags\n");
    printf ("\t-t CSV output\n");
    printf ("example:\n\tcheckit_tiff example_configs/baseline_minimal.cfg tiffs_should_pass/minimal_valid.tiff \n");
    printf ("\n");
}

/** iterate through all files of given directory */
int validate_dir(int are_valid, int flag_use_memorymapped_io, int flag_scan_recursively, const char *cfg_file,
                 const char *tiff_file_or_dir) {
    size_t len = strlen(tiff_file_or_dir);
    char tiff_dir[len + 1];
    cit_strcopy(tiff_dir, sizeof(tiff_dir), tiff_file_or_dir, len);
    /* remove trailing / */
    char const *dirsuffix = strrchr(tiff_dir, '/');
    if (dirsuffix != NULL) { /* found a / */
        if (0 == strcmp(dirsuffix, "/")) { /* ok, ends with / */
            /* remove last / */
            assert(len >= 1); // or whatever you want to do with short strings
            tiff_dir[len - 1] = 0;
        }
    }
    /* iterate through all files in given dir */
    DIR *dir;
    if ((dir = opendir(tiff_file_or_dir)) != NULL) {
        /* print all the files and directories within directory */
        struct dirent *ent;
        while ((ent = readdir(dir)) != NULL) {
            if (0 == strcmp(ent->d_name, "..") ) {
                //printf ("PARENT DETECTED (%s)\n", ent->d_name);
                continue;
            }
            if (0 == strcmp(ent->d_name, ".") ) {
                //printf ("CURRENT DETECTED (%s)\n", ent->d_name);
                continue;
            }
            struct stat attribute;
            len = strlen(tiff_dir) + strlen(ent->d_name) + 2;
            char fqname[len];
            snprintf(fqname, len, "%s/%s", tiff_dir, ent->d_name);
            if (stat(fqname, &attribute) == -1) {
                fprintf(stderr, "could not stat on file '%s' in directory '%s' (%s)\n", ent->d_name, tiff_dir,
                        fqname);
                exit(EXIT_FAILURE);
            } else if (attribute.st_mode & S_IFREG) {
                //printf ("%s\n", fqname);
                parse_plan_via_file(cfg_file);
                are_valid += check_specific_tiff_file(fqname, flag_use_memorymapped_io);
                clean_plan();
                printf("\n");
            } else if (flag_scan_recursively) {
                are_valid += validate_dir(are_valid, flag_use_memorymapped_io, flag_scan_recursively, cfg_file,
                                          fqname);
            }
        }
        closedir(dir);
    } else {
        /* could not open directory */
        fprintf(stderr, "directory '%s' could not be opened\n", tiff_file_or_dir);
        exit(EXIT_FAILURE);
    }
    return are_valid;
}

/** main */
int main (int argc, char * argv[]) {
    FILE *default_filehandle = stdout;
    int c;
    int flag_check_directory = UNFLAGGED;
    int flag_check_directory_recursively = UNFLAGGED;
    int flag_use_memorymapped_io = UNFLAGGED;
    while ((c = getopt(argc, argv, "chmdDx:qt")) != -1) {
        switch (c) {
            case 'h': /* help */
                help(default_filehandle, argv[0]);
                exit(0);
            case 'c': /*  colorize output */
                set_renderer_to_ansi();
                break;
            case 'd': /* check directory */
                flag_check_directory = FLAGGED;
                flag_check_directory_recursively = UNFLAGGED;
//                printf("\nCheck all files in given directory\n");
                break;
            case 'D': /* check directory */
                flag_check_directory = FLAGGED;
                flag_check_directory_recursively = FLAGGED;
//                printf("\nCheck all files in given directory, recursively\n");
                break;
            case 'm': /* use memory mapped I/O */
                flag_use_memorymapped_io = FLAGGED;
                break;
            case 'q': /* suppresses output of valid rules/tags */
                set_renderer_to_quiet();
                break;
            case 't':
                default_filehandle = stderr;
                set_renderer_to_csv();
                break;
            case '?': /* something goes wrong */
                /*
                if (optopt == 'r') {
                fprintf (stderr, "Option -%c requires an argument.\n", optopt);
                return (-1);
                }
                else*/
                if (isprint(optopt)) {
                    fprintf(stderr, "Unknown option `-%c'.\n", optopt);
                    return (-1);
                } else if (0 != optopt) {
                    fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt);
                    return (-1);
                }
                break;
            default:
                abort();
        }
    }
    if (argc - optind < 2) {
        help(default_filehandle, argv[0]);
        fprintf(stderr,
                "%s needs at least two arguments, first should be the config-file, second the TIFF-file\nexample:\n\t %s example_configs/baseline_minimal.cfg tiffs_should_pass/minimal_valid.tiff\n",
                argv[0], argv[0]);
        exit(EXIT_FAILURE);
    }
    const char *cfg_file = argv[optind];
    print_program_header(default_filehandle, argv[0]);
    fprintf(default_filehandle, "cfg_file=%s\n", cfg_file);
    int are_valid = 0;
    int index_of_tiff_file_or_dir = optind + 1;
    do {
        const char *tiff_file_or_dir = argv[index_of_tiff_file_or_dir++];
        fprintf(default_filehandle, "tiff file/dir=%s\n", tiff_file_or_dir);


        if (flag_check_directory == FLAGGED) {
            are_valid = validate_dir(are_valid, flag_use_memorymapped_io, flag_check_directory_recursively, cfg_file, tiff_file_or_dir);
        } else { /* a file */
            /* use tiff_file_or_dir */
            parse_plan_via_file(cfg_file);
            are_valid += check_specific_tiff_file(tiff_file_or_dir, flag_use_memorymapped_io);
            clean_plan();
        }
    } while (index_of_tiff_file_or_dir < argc);
    if (0 == are_valid) {
        exit(EXIT_SUCCESS);
    } else {
        exit(EXIT_FAILURE);
    }
}


/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 smarttab expandtab :*/
