#include "system.h"
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "keccak.h"

/* What kind of changes a hunk contains.  */
enum changes {
    /* No changes: lines common to both files.  */
            UNCHANGED,

    /* Deletes only: lines taken from just the first file.  */
            OLD,

    /* Inserts only: lines taken from just the second file.  */
            NEW,

    /* Both deletes and inserts: a hunk containing both old and new lines.  */
            CHANGED
};

/* Variables for command line options */

#ifndef GDIFF_MAIN
# define XTERN extern
#else
# define XTERN
#endif

/* Number of lines of context to show in each set of diffs.  This is
   zero when context is not to be shown.  */
XTERN lin context;

/* Consider all files as text files (-a).  Don't interpret codes over
   0177 as implying a "binary file".  */
XTERN bool text;

/* Number of lines to keep in identical prefix and suffix.  */
XTERN lin horizon_lines;

/* Files can be compared byte-by-byte, as if they were binary.  This
   depends on various options.  */
XTERN bool files_can_be_treated_as_binary;

/* File labels for |-c| output headers (|--label|).  */
XTERN char *file_label[2];

/* Say only whether files differ, not how (|-q|).  */
XTERN bool brief;

/* Do not output an initial space or tab before the text of an empty line.  */
XTERN bool suppress_blank_empty;

/* In directory comparison, specify file to start with (|-S|).  This
   is used for resuming an aborted comparison.  All file names less
   than this name are ignored.  */
XTERN char const *starting_file;

/* String containing all the command options diff received, with
   spaces between and at the beginning but none at the end.  If there
   were no options given, this string is empty.  */
XTERN char *switch_string;

/* Use heuristics for better speed with large files with a small
   density of changes.  */
XTERN bool speed_large_files;

/* Don't discard lines.  This makes things slower (sometimes much
   slower) but will find a guaranteed minimal set of changes.  */
XTERN bool minimal;

/* The result of comparison is an ``edit script'': a chain of |struct
   change|.  Each |struct change| represents one place where some
   lines are deleted and some are inserted.

   |line0| and |line1| are the first affected lines in the two files
   (origin 0).  |deleted| is the number of lines deleted here from file
   0.  |inserted| is the number of lines inserted here in file 1.

   If |deleted| is 0 then |line0| is the number of the line before
   which the insertion was done; vice versa for |inserted| and
   |line1|.  */

struct change {
    struct change *link;        /* Previous or next edit command  */
    lin inserted;            /* \# lines of file 1 changed here.  */
    lin deleted;            /* \# lines of file 0 changed here.  */
    lin line0;            /* Line number of 1st deleted line.  */
    lin line1;            /* Line number of 1st inserted line.  */
    bool ignore;            /* Flag used in |context.c|.  */
};

/* Structures that describe the input files.  */

/* Data on one input file being compared.  */

struct file_data {
    int desc;    /* File descriptor  */
    char const *name;    /* File name  */
    struct stat stat;    /* File status */

    /* Buffer in which text of file is read.  */
    word *buffer;

    /* Allocated size of buffer, in bytes.  Always a multiple of
       sizeof |*buffer|.  */
    size_t bufsize;

    /* Number of valid bytes now in the buffer.  */
    size_t buffered;

    /* Array of pointers to lines in the file.  */
    char const **linbuf;

    /* |linbuf_base <= buffered_lines <= valid_lines <= alloc_lines|.
       |linebuf[linbuf_base ... buffered_lines - 1]| are possibly differing.
       |linebuf[linbuf_base ... valid_lines - 1]| contain valid data.
       |linebuf[linbuf_base ... alloc_lines - 1]| are allocated.  */
    lin linbuf_base, buffered_lines, valid_lines, alloc_lines;

    /* Pointer to end of prefix of this file to ignore when hashing.  */
    char const *prefix_end;

    /* Count of lines in the prefix.  There are this many lines in the
       file before |linbuf[0]|.  */
    lin prefix_lines;

    /* Pointer to start of suffix of this file to ignore when hashing.  */
    char const *suffix_begin;

    /* Vector, indexed by line number, containing an equivalence code
       for each line.  It is this vector that is actually compared
       with that of another file to generate differences.  */
    lin *equivs;

    /* Vector, like the previous one except that the elements for
       discarded lines have been squeezed out.  */
    lin *undiscarded;

    /* Vector mapping virtual line numbers (not counting discarded
       lines) to real ones (counting those lines).  Both are
       $origin-0$.  */
    lin *realindexes;

    /* Total number of nondiscarded lines.  */
    lin nondiscarded_lines;

    /* Vector, indexed by real $origin-0$ line number, containing 1
       for a line that is an insertion or a deletion.  The results of
       comparison are stored here.  */
    char *changed;

    /* 1 if file ends in a line with no final newline.  */
    bool missing_newline;

    /* 1 if at end of file.  */
    bool eof;

    /* 1 more than the maximum equivalence value used for this or its
       sibling file.  */
    lin equiv_max;

    /* keccak context */
    void *hash_context;
    uint64_t hash[8];
};

/* The file buffer, considered as an array of bytes rather than as an
   array of words.  */
#define FILE_BUFFER(f) ((char *) (f)->buffer)

/* Data on two input files being compared.  */

struct comparison {
    struct file_data file[2];
    struct comparison const *parent;  /* parent, if a recursive comparison */
};

/* Describe the two files currently being compared.  */

XTERN struct file_data files[2];

/* Stdio stream to output diffs to.  */

XTERN FILE *outfile;

/* Declare various functions.  */

/* analyze.c */
extern int diff_2_files(struct comparison *);

/* context.c */
extern void print_context_header(struct file_data[], char const *const *);

extern void print_context_script(struct change *);

/* dir.c */
extern int diff_dirs(struct comparison const *,
                     int (*)(struct comparison const *,
                             char const *, char const *));

extern char *find_dir_file_pathname(char const *, char const *);

/* diff.h */
extern struct change *find_hunk(struct change *);

extern void pr_unidiff_hunk(struct change *);

/* io.c */
extern void file_block_read(struct file_data *, size_t);

extern bool read_files(struct file_data[], bool);

/* util.c */

extern bool lines_differ(char const *, char const *);

extern lin translate_line_number(struct file_data const *, lin);

extern struct change *find_change(struct change *);

extern void *zalloc(size_t);

extern enum changes analyze_hunk(struct change *, lin *, lin *, lin *, lin *);

extern void begin_output(void);

extern void debug_script(struct change *);

extern void fatal(char const *) __attribute__((noreturn));

extern void finish_output(void);

extern void message(char const *, char const *, char const *);

extern void message5(char const *, char const *, char const *,
                     char const *, char const *);

extern void output_1_line(char const *, char const *);

extern void perror_with_name(char const *);

extern void pfatal_with_name(char const *) __attribute__((noreturn));

extern void print_1_line(char const *, char const *const *);

extern void print_1_line_nl(char const *, char const *const *, bool);

extern void print_script(struct change *);

extern void setup_output(char const *, char const *, bool);

extern void translate_range(struct file_data const *, lin, lin,
                            printint *, printint *);