/* vdelta-test.c -- test driver for text deltas
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */

#define APR_WANT_STDIO
#include <apr_want.h>

#include <apr_general.h>
#include <assert.h>

#include "../svn_test.h"

#include "svn_ctype.h"
#include "svn_delta.h"
#include "svn_error.h"
#include "svn_pools.h"

#include "../../libsvn_delta/delta.h"
#include "delta-window-test.h"

static apr_off_t
print_delta_window(const svn_txdelta_window_t *window,
                   const char *tag, int quiet, FILE *stream)
{
  if (quiet)
    return delta_window_size_estimate(window);
  else
    return delta_window_print(window, tag, stream);
}


static void
do_one_diff(apr_file_t *source_file, apr_file_t *target_file,
            int *count, apr_off_t *len,
            int quiet, apr_pool_t *pool,
            const char *tag, FILE* stream)
{
  svn_txdelta_stream_t *delta_stream = NULL;
  svn_txdelta_window_t *delta_window = NULL;
  apr_pool_t *fpool = svn_pool_create(pool);
  apr_pool_t *wpool = svn_pool_create(pool);

  *count = 0;
  *len = 0;
  svn_txdelta2(&delta_stream,
               svn_stream_from_aprfile(source_file, fpool),
               svn_stream_from_aprfile(target_file, fpool),
               FALSE,
               fpool);
  do {
    svn_error_t *err;
    err = svn_txdelta_next_window(&delta_window, delta_stream, wpool);
    if (err)
      svn_handle_error2(err, stderr, TRUE, "vdelta-test: ");
    if (delta_window != NULL)
      {
        *len += print_delta_window(delta_window, tag, quiet, stream);
        svn_pool_clear(wpool);
        ++*count;
      }
  } while (delta_window != NULL);
  fprintf(stream, "%s: (LENGTH %" APR_OFF_T_FMT " +%d)\n", tag, *len, *count);

  svn_pool_destroy(fpool);
  svn_pool_destroy(wpool);
}


static void
do_one_test_cycle(apr_file_t *source_file_A, apr_file_t *target_file_A,
                  apr_file_t *source_file_B, apr_file_t *target_file_B,
                  int quiet, apr_pool_t *pool)
{
  int count_A = 0;
  apr_off_t len_A = 0;

  int count_B = 0;
  apr_off_t len_B = 0;

  do_one_diff(source_file_A, target_file_A,
              &count_A, &len_A, quiet, pool, "A ", stdout);

  if (source_file_B)
    {
      apr_pool_t *fpool = svn_pool_create(pool);
      apr_pool_t *wpool = svn_pool_create(pool);
      svn_txdelta_stream_t *stream_A = NULL;
      svn_txdelta_stream_t *stream_B = NULL;
      svn_txdelta_window_t *window_A = NULL;
      svn_txdelta_window_t *window_B = NULL;
      svn_txdelta_window_t *window_AB = NULL;
      int count_AB = 0;
      apr_off_t len_AB = 0;

      putc('\n', stdout);
      do_one_diff(source_file_B, target_file_B,
                  &count_B, &len_B, quiet, pool, "B ", stdout);

      putc('\n', stdout);

      {
        apr_off_t offset = 0;

        apr_file_seek(source_file_A, APR_SET, &offset);
        apr_file_seek(target_file_A, APR_SET, &offset);
        apr_file_seek(source_file_B, APR_SET, &offset);
        apr_file_seek(target_file_B, APR_SET, &offset);
      }

      svn_txdelta2(&stream_A,
                   svn_stream_from_aprfile(source_file_A, fpool),
                   svn_stream_from_aprfile(target_file_A, fpool),
                   FALSE,
                   fpool);
      svn_txdelta2(&stream_B,
                   svn_stream_from_aprfile(source_file_B, fpool),
                   svn_stream_from_aprfile(target_file_B, fpool),
                   FALSE,
                   fpool);

      for (count_AB = 0; count_AB < count_B; ++count_AB)
        {
          svn_error_t *err;

          err = svn_txdelta_next_window(&window_A, stream_A, wpool);
          if (err)
            svn_handle_error2(err, stderr, TRUE, "vdelta-test: ");
          err = svn_txdelta_next_window(&window_B, stream_B, wpool);
          if (err)
            svn_handle_error2(err, stderr, TRUE, "vdelta-test: ");

          /* Note: It's not possible that window_B is null, we already
             counted the number of windows in the second delta. */
          assert(window_A != NULL || window_B->src_ops == 0);
          if (window_B->src_ops == 0)
            {
              window_AB = window_B;
              window_AB->sview_len = 0;
            }
          else
            window_AB = svn_txdelta_compose_windows(window_A, window_B,
                                                    wpool);
          len_AB += print_delta_window(window_AB, "AB", quiet, stdout);
          svn_pool_clear(wpool);
        }

      fprintf(stdout, "AB: (LENGTH %" APR_OFF_T_FMT " +%d)\n",
              len_AB, count_AB);
    }
}


static apr_file_t *
open_binary_read(const char *path, apr_pool_t *pool)
{
  apr_status_t apr_err;
  apr_file_t *fp;

  apr_err = apr_file_open(&fp, path, (APR_READ | APR_BINARY),
                          APR_OS_DEFAULT, pool);

  if (apr_err)
    {
      fprintf(stderr, "unable to open \"%s\" for reading\n", path);
      exit(1);
    }

  return fp;
}


int
main(int argc, char **argv)
{
  apr_file_t *source_file_A = NULL;
  apr_file_t *target_file_A = NULL;

  apr_file_t *source_file_B = NULL;
  apr_file_t *target_file_B = NULL;

  apr_pool_t *pool;
  int quiet = 0;
  int repeat = 1;

  while (argc > 1)
    {
      const char *const arg = argv[1];
      if (arg[0] != '-')
        break;

      if (arg[1] == 'q')
        quiet = 1;
      else if (svn_ctype_isdigit(arg[1]))
        repeat = atoi(arg + 1);
      else
        break;
      --argc; ++argv;
    }

  apr_initialize();
  pool = svn_pool_create(NULL);

  if (argc == 2)
    {
      target_file_A = open_binary_read(argv[1], pool);
    }
  else if (argc == 3)
    {
      source_file_A = open_binary_read(argv[1], pool);
      target_file_A = open_binary_read(argv[2], pool);
    }
  else if (argc == 4)
    {
      source_file_A = open_binary_read(argv[1], pool);
      target_file_A = open_binary_read(argv[2], pool);
      source_file_B = open_binary_read(argv[2], pool);
      target_file_B = open_binary_read(argv[3], pool);
    }
  else
    {
      fprintf(stderr,
              "Usage: vdelta-test [-q] [-<repeat>] <target>\n"
              "   or: vdelta-test [-q] [-<repeat>] <source> <target>\n"
              "   or: vdelta-test [-q] [-<repeat>] "
              "<source> <intermediate> <target>\n");
      exit(1);
    }

  while (0 < repeat--)
    {
      apr_off_t offset = 0;

      do_one_test_cycle(source_file_A, target_file_A,
                        source_file_B, target_file_B,
                        quiet, pool);

      if (source_file_A) apr_file_seek(source_file_A, APR_SET, &offset);
      if (target_file_A) apr_file_seek(target_file_A, APR_SET, &offset);
      if (source_file_B) apr_file_seek(source_file_B, APR_SET, &offset);
      if (target_file_B) apr_file_seek(target_file_B, APR_SET, &offset);
    }

  if (source_file_A) apr_file_close(source_file_A);
  if (target_file_A) apr_file_close(target_file_A);
  if (source_file_B) apr_file_close(source_file_B);
  if (target_file_B) apr_file_close(source_file_B);

  svn_pool_destroy(pool);
  apr_terminate();
  exit(0);
}
