%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
%-----------------------------------------------------------------------------%
% Copyright (C) 1995-1998, 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
% 
% Main authors: bromage, Marnix Klooster <marnix@worldonline.nl>
% 
% Something very similar to the standard diff utility.  Sort of.  :-)
% 
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- module diff.

:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- implementation.

:- import_module diff_out.
:- import_module file.
:- import_module filter.
:- import_module globals.
:- import_module match.
:- import_module myers.
:- import_module options.

:- import_module getopt.
:- import_module list.
:- import_module maybe.

%-----------------------------------------------------------------------------%

    % main: top-level predicate.
    %
main(!IO) :-
    io.command_line_arguments(Args0, !IO),
    options.get_option_ops(OptionOps),
    getopt.process_options(OptionOps, Args0, Args, Result0),
    postprocess_options(Result0, Result, !IO), 
    (
        Result = yes(Msg),
        usage_error(Msg, !IO)
    ;
        Result = no,
        globals.io_get_output_style(OutputStyle, !IO),
        ( OutputStyle = help_only ->
            usage(!IO)
        ; OutputStyle = version_only ->
            version(!IO)
        ;
            main_2(Args, !IO)
        )
    ).

%-----------------------------------------------------------------------------%

:- pred usage_error(string::in, io::di, io::uo) is det.

usage_error(Msg, !IO) :-
    io.progname_base("diff", ProgName, !IO),
    io.stderr_stream(StdErr, !IO),
    io.write_strings(StdErr, [ProgName, ": ", Msg, "\n"], !IO),
    io.set_exit_status(1, !IO),
    usage(!IO).

:- pred usage_io_error(io.error::in, io::di, io::uo) is det.

usage_io_error(Error, !IO) :-
    io.error_message(Error, Msg),
    usage_error(Msg, !IO).

:- pred usage(io::di, io::uo) is det.

usage(!IO) :-
    io.write_string("Usage: diff [options] from-file to-file\n\n", !IO),
    options_help(!IO).

:- pred version(io::di, io::uo) is det.

version(!IO) :-
    io.write_string("diff - Mercury diff version 0.4\n", !IO).

%-----------------------------------------------------------------------------%

    % main_2 analyses the command-line arguments which are not
    % options and calls diff.do_diff.
    %
:- pred main_2(list(string)::in, io::di, io::uo) is det.

main_2([], !IO) :-
    usage_error("missing operand", !IO).
main_2([Fname1 | Rest], !IO)  :-
    ( 
        Rest = [Fname2 | _],
        ( Fname1 = Fname2 ->
            % Not sure why anyone would want to diff two
            % files with the same name, but just in case ...
            ( Fname1 = "-" ->
                file.read_input(Fname1, Contents1, !IO),
                Contents1 = Contents2
            ;
                file.read_file(Fname1, Contents1, !IO),
                Contents1 = Contents2
            )
        ;
            % If either file is "-", simply use standard input.
            % (Note: Both can't be "-" since that was dealt with
            % in the previous case.)
            ( Fname1 = "-" ->
                file.read_input(Fname1, Contents1, !IO),
                file.read_file(Fname2, Contents2, !IO)
            ; Fname2 = "-" ->
                file.read_file(Fname1, Contents1, !IO),
                file.read_input(Fname2, Contents2, !IO)
            ;
                % Otherwise read the files normally.
                file.read_file(Fname1, Contents1, !IO),
                file.read_file(Fname2, Contents2, !IO)
            )
        ),
        % Now do the diff.
        (
            Contents1 = ok(File1),
            (
                Contents2 = ok(File2),
                diff.do_diff(File1, File2, !IO)
            ;
                Contents2 = error(Msg),
                usage_io_error(Msg, !IO)
            )
        ;
            Contents1 = error(Msg),
            usage_io_error(Msg, !IO)
        )
    ;
        Rest = [],
        usage_error("missing operand", !IO)
    ).

%-----------------------------------------------------------------------------%

    % do_diff takes the files plus all the command
    % line options and determines what to do with them.
    %
    % At the moment, we're organised into four passes:
    %
    %   - build_matches determines which lines from the
    %     input files match (using the appropriate command-
    %     line options).
    %   - diff_by_myers takes the matches produced and
    %     computes a diff between them.
    %   - filter_diff analyses the diff, filtering out
    %     any edits which the user said that they didn't
    %     want to see (using the appropriate command-line
    %     options).
    %   - display_diff outputs the diff in whatever output
    %     format the user chose.
    %
:- pred do_diff(file::in, file::in, io::di, io::uo) is det.

do_diff(File1, File2, !IO) :-
    build_matches(File1, File2, FileX, FileY, !IO),
    diff_by_myers(FileX, FileY, Diff0, !IO),
    filter_diff(File1, File2, Diff0, Diff, !IO),
    display_diff(File1, File2, Diff, !IO).

%-----------------------------------------------------------------------------%
:- end_module diff.
%-----------------------------------------------------------------------------%
