/*
 *  Copyright 2011 The LibYuv Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "unit_test/unit_test.h"
#include "libyuv/basic_types.h"
#include "libyuv/compare.h"
#include "libyuv/cpu_id.h"

namespace libyuv {

// hash seed of 5381 recommended.
static uint32 ReferenceHashDjb2(const uint8* src, uint64 count, uint32 seed) {
  uint32 hash = seed;
  if (count > 0) {
    do {
      hash = hash * 33 + *src++;
    } while (--count);
  }
  return hash;
}

TEST_F(libyuvTest, TestDjb2) {
  const int kMaxTest = 2049;
  align_buffer_16(src_a, kMaxTest)

  for (int i = 0; i < kMaxTest; ++i) {
    src_a[i] = i;
  }
  for (int i = 0; i < kMaxTest; ++i) {
    uint32 h1 = HashDjb2(src_a, kMaxTest, 5381);
    uint32 h2 = ReferenceHashDjb2(src_a, kMaxTest, 5381);
    EXPECT_EQ(h1, h2);
  }
  // Hash constant generator using for tables in compare
  int h = 1;
  for (int i = 0; i <= 16 ; ++i) {
    printf("%08x ", h);
    h *= 33;
  }
  printf("\n");

  free_aligned_buffer_16(src_a)
}

TEST_F(libyuvTest, BenchmakDjb2_C) {
  const int kMaxTest = 1280 * 720;
  align_buffer_16(src_a, kMaxTest)

  for (int i = 0; i < kMaxTest; ++i) {
    src_a[i] = i;
  }
  uint32 h2 = ReferenceHashDjb2(src_a, kMaxTest, 5381);
  uint32 h1;
  MaskCpuFlags(kCpuInitialized);
  for (int i = 0; i < benchmark_iterations_; ++i) {
    h1 = HashDjb2(src_a, kMaxTest, 5381);
  }
  MaskCpuFlags(-1);
  EXPECT_EQ(h1, h2);
  free_aligned_buffer_16(src_a)
}

TEST_F(libyuvTest, BenchmakDjb2_OPT) {
  const int kMaxTest = 1280 * 720;
  align_buffer_16(src_a, kMaxTest)

  for (int i = 0; i < kMaxTest; ++i) {
    src_a[i] = i;
  }
  uint32 h2 = ReferenceHashDjb2(src_a, kMaxTest, 5381);
  uint32 h1;
  for (int i = 0; i < benchmark_iterations_; ++i) {
    h1 = HashDjb2(src_a, kMaxTest, 5381);
  }
  EXPECT_EQ(h1, h2);
  free_aligned_buffer_16(src_a)
}

TEST_F(libyuvTest, BenchmakDjb2_Unaligned_OPT) {
  const int kMaxTest = 1280 * 720;
  align_buffer_16(src_a, kMaxTest + 1)

  for (int i = 0; i < kMaxTest; ++i) {
    src_a[i + 1] = i;
  }
  uint32 h2 = ReferenceHashDjb2(src_a + 1, kMaxTest, 5381);
  uint32 h1;
  for (int i = 0; i < benchmark_iterations_; ++i) {
    h1 = HashDjb2(src_a + 1, kMaxTest, 5381);
  }
  EXPECT_EQ(h1, h2);
  free_aligned_buffer_16(src_a)
}

TEST_F(libyuvTest, BenchmarkSumSquareError_C) {
  const int kMaxWidth = 4096 * 3;
  align_buffer_16(src_a, kMaxWidth)
  align_buffer_16(src_b, kMaxWidth)

  for (int i = 0; i < kMaxWidth; ++i) {
    src_a[i] = i;
    src_b[i] = i;
  }

  MaskCpuFlags(kCpuInitialized);
  for (int i = 0; i < benchmark_iterations_; ++i) {
    ComputeSumSquareError(src_a, src_b, kMaxWidth);
  }

  MaskCpuFlags(-1);

  EXPECT_EQ(0, 0);

  free_aligned_buffer_16(src_a)
  free_aligned_buffer_16(src_b)
}

TEST_F(libyuvTest, BenchmarkSumSquareError_OPT) {
  const int kMaxWidth = 4096 * 3;
  align_buffer_16(src_a, kMaxWidth)
  align_buffer_16(src_b, kMaxWidth)

  for (int i = 0; i < kMaxWidth; ++i) {
    src_a[i] = i;
    src_b[i] = i;
  }

  for (int i = 0; i < benchmark_iterations_; ++i) {
    ComputeSumSquareError(src_a, src_b, kMaxWidth);
  }

  EXPECT_EQ(0, 0);

  free_aligned_buffer_16(src_a)
  free_aligned_buffer_16(src_b)
}

TEST_F(libyuvTest, SumSquareError) {
  const int kMaxWidth = 4096 * 3;
  align_buffer_16(src_a, kMaxWidth)
  align_buffer_16(src_b, kMaxWidth)

  memset(src_a, 0, kMaxWidth);
  memset(src_b, 0, kMaxWidth);

  uint64 err;
  err = ComputeSumSquareError(src_a, src_b, kMaxWidth);

  EXPECT_EQ(err, 0);

  memset(src_a, 1, kMaxWidth);
  err = ComputeSumSquareError(src_a, src_b, kMaxWidth);

  EXPECT_EQ(err, kMaxWidth);

  memset(src_a, 190, kMaxWidth);
  memset(src_b, 193, kMaxWidth);
  err = ComputeSumSquareError(src_a, src_b, kMaxWidth);

  EXPECT_EQ(err, (kMaxWidth * 3 * 3));

  srandom(time(NULL));

  for (int i = 0; i < kMaxWidth; ++i) {
    src_a[i] = (random() & 0xff);
    src_b[i] = (random() & 0xff);
  }

  MaskCpuFlags(kCpuInitialized);
  uint64 c_err = ComputeSumSquareError(src_a, src_b, kMaxWidth);

  MaskCpuFlags(-1);
  uint64 opt_err = ComputeSumSquareError(src_a, src_b, kMaxWidth);

  EXPECT_EQ(c_err, opt_err);

  free_aligned_buffer_16(src_a)
  free_aligned_buffer_16(src_b)
}

TEST_F(libyuvTest, BenchmarkPsnr_C) {
  align_buffer_16(src_a, benchmark_width_ * benchmark_height_)
  align_buffer_16(src_b, benchmark_width_ * benchmark_height_)

  for (int i = 0; i < benchmark_width_ * benchmark_height_; ++i) {
    src_a[i] = i;
    src_b[i] = i;
  }

  MaskCpuFlags(kCpuInitialized);

  double c_time = get_time();
  for (int i = 0; i < benchmark_iterations_; ++i)
    CalcFramePsnr(src_a, benchmark_width_,
                  src_b, benchmark_width_,
                  benchmark_width_, benchmark_height_);

  c_time = (get_time() - c_time) / benchmark_iterations_;
  printf("BenchmarkPsnr_C - %8.2f us c\n", c_time * 1e6);

  MaskCpuFlags(-1);

  EXPECT_EQ(0, 0);

  free_aligned_buffer_16(src_a)
  free_aligned_buffer_16(src_b)
}

TEST_F(libyuvTest, BenchmarkPsnr_OPT) {
  align_buffer_16(src_a, benchmark_width_ * benchmark_height_)
  align_buffer_16(src_b, benchmark_width_ * benchmark_height_)

  for (int i = 0; i < benchmark_width_ * benchmark_height_; ++i) {
    src_a[i] = i;
    src_b[i] = i;
  }

  MaskCpuFlags(-1);

  double opt_time = get_time();
  for (int i = 0; i < benchmark_iterations_; ++i)
    CalcFramePsnr(src_a, benchmark_width_,
                  src_b, benchmark_width_,
                  benchmark_width_, benchmark_height_);

  opt_time = (get_time() - opt_time) / benchmark_iterations_;
  printf("BenchmarkPsnr_OPT - %8.2f us opt\n", opt_time * 1e6);

  EXPECT_EQ(0, 0);

  free_aligned_buffer_16(src_a)
  free_aligned_buffer_16(src_b)
}

TEST_F(libyuvTest, Psnr) {
  const int kSrcWidth = 1280;
  const int kSrcHeight = 720;
  const int b = 128;
  const int kSrcPlaneSize = (kSrcWidth + b * 2) * (kSrcHeight + b * 2);
  const int kSrcStride = 2 * b + kSrcWidth;
  align_buffer_16(src_a, kSrcPlaneSize)
  align_buffer_16(src_b, kSrcPlaneSize)

  memset(src_a, 0, kSrcPlaneSize);
  memset(src_b, 0, kSrcPlaneSize);

  double err;
  err = CalcFramePsnr(src_a + kSrcStride * b + b, kSrcStride,
                      src_b + kSrcStride * b + b, kSrcStride,
                      kSrcWidth, kSrcHeight);

  EXPECT_EQ(err, kMaxPsnr);

  memset(src_a, 255, kSrcPlaneSize);

  err = CalcFramePsnr(src_a + kSrcStride * b + b, kSrcStride,
                      src_b + kSrcStride * b + b, kSrcStride,
                      kSrcWidth, kSrcHeight);

  EXPECT_EQ(err, 0.0);

  memset(src_a, 1, kSrcPlaneSize);

  err = CalcFramePsnr(src_a + kSrcStride * b + b, kSrcStride,
                      src_b + kSrcStride * b + b, kSrcStride,
                      kSrcWidth, kSrcHeight);

  EXPECT_GT(err, 48.0);
  EXPECT_LT(err, 49.0);

  for (int i = 0; i < kSrcPlaneSize; ++i)
    src_a[i] = i;

  err = CalcFramePsnr(src_a + kSrcStride * b + b, kSrcStride,
                      src_b + kSrcStride * b + b, kSrcStride,
                      kSrcWidth, kSrcHeight);

  EXPECT_GT(err, 4.0);
  EXPECT_LT(err, 5.0);

  srandom(time(NULL));

  memset(src_a, 0, kSrcPlaneSize);
  memset(src_b, 0, kSrcPlaneSize);

  for (int i = b; i < (kSrcHeight + b); ++i) {
    for (int j = b; j < (kSrcWidth + b); ++j) {
      src_a[(i * kSrcStride) + j] = (random() & 0xff);
      src_b[(i * kSrcStride) + j] = (random() & 0xff);
    }
  }

  MaskCpuFlags(kCpuInitialized);
  double c_err, opt_err;

  c_err = CalcFramePsnr(src_a + kSrcStride * b + b, kSrcStride,
                        src_b + kSrcStride * b + b, kSrcStride,
                        kSrcWidth, kSrcHeight);

  MaskCpuFlags(-1);

  opt_err = CalcFramePsnr(src_a + kSrcStride * b + b, kSrcStride,
                          src_b + kSrcStride * b + b, kSrcStride,
                          kSrcWidth, kSrcHeight);

  EXPECT_EQ(opt_err, c_err);

  free_aligned_buffer_16(src_a)
  free_aligned_buffer_16(src_b)
}

TEST_F(libyuvTest, BenchmarkSsim_C) {
  align_buffer_16(src_a, benchmark_width_ * benchmark_height_)
  align_buffer_16(src_b, benchmark_width_ * benchmark_height_)

  for (int i = 0; i < benchmark_width_ * benchmark_height_; ++i) {
    src_a[i] = i;
    src_b[i] = i;
  }

  MaskCpuFlags(kCpuInitialized);

  double c_time = get_time();
  for (int i = 0; i < benchmark_iterations_; ++i)
    CalcFrameSsim(src_a, benchmark_width_,
                  src_b, benchmark_width_,
                  benchmark_width_, benchmark_height_);

  c_time = (get_time() - c_time) / benchmark_iterations_;
  printf("BenchmarkSsim_C - %8.2f us c\n", c_time * 1e6);

  MaskCpuFlags(-1);

  EXPECT_EQ(0, 0);

  free_aligned_buffer_16(src_a)
  free_aligned_buffer_16(src_b)
}

TEST_F(libyuvTest, BenchmarkSsim_OPT) {
  align_buffer_16(src_a, benchmark_width_ * benchmark_height_)
  align_buffer_16(src_b, benchmark_width_ * benchmark_height_)

  for (int i = 0; i < benchmark_width_ * benchmark_height_; ++i) {
    src_a[i] = i;
    src_b[i] = i;
  }

  MaskCpuFlags(-1);

  double opt_time = get_time();
  for (int i = 0; i < benchmark_iterations_; ++i)
    CalcFrameSsim(src_a, benchmark_width_,
                  src_b, benchmark_width_,
                  benchmark_width_, benchmark_height_);

  opt_time = (get_time() - opt_time) / benchmark_iterations_;
  printf("BenchmarkPsnr_OPT - %8.2f us opt\n", opt_time * 1e6);

  EXPECT_EQ(0, 0);

  free_aligned_buffer_16(src_a)
  free_aligned_buffer_16(src_b)
}

TEST_F(libyuvTest, Ssim) {
  const int kSrcWidth = 1280;
  const int kSrcHeight = 720;
  const int b = 128;
  const int kSrcPlaneSize = (kSrcWidth + b * 2) * (kSrcHeight + b * 2);
  const int kSrcStride = 2 * b + kSrcWidth;
  align_buffer_16(src_a, kSrcPlaneSize)
  align_buffer_16(src_b, kSrcPlaneSize)

  memset(src_a, 0, kSrcPlaneSize);
  memset(src_b, 0, kSrcPlaneSize);

  double err;
  err = CalcFrameSsim(src_a + kSrcStride * b + b, kSrcStride,
                      src_b + kSrcStride * b + b, kSrcStride,
                      kSrcWidth, kSrcHeight);

  EXPECT_EQ(err, 1.0);

  memset(src_a, 255, kSrcPlaneSize);

  err = CalcFrameSsim(src_a + kSrcStride * b + b, kSrcStride,
                      src_b + kSrcStride * b + b, kSrcStride,
                      kSrcWidth, kSrcHeight);

  EXPECT_LT(err, 0.0001);

  memset(src_a, 1, kSrcPlaneSize);

  err = CalcFrameSsim(src_a + kSrcStride * b + b, kSrcStride,
                      src_b + kSrcStride * b + b, kSrcStride,
                      kSrcWidth, kSrcHeight);

  EXPECT_GT(err, 0.8);
  EXPECT_LT(err, 0.9);

  for (int i = 0; i < kSrcPlaneSize; ++i)
    src_a[i] = i;

  err = CalcFrameSsim(src_a + kSrcStride * b + b, kSrcStride,
                      src_b + kSrcStride * b + b, kSrcStride,
                      kSrcWidth, kSrcHeight);

  EXPECT_GT(err, 0.008);
  EXPECT_LT(err, 0.009);

  srandom(time(NULL));
  for (int i = b; i < (kSrcHeight + b); ++i) {
    for (int j = b; j < (kSrcWidth + b); ++j) {
      src_a[(i * kSrcStride) + j] = (random() & 0xff);
      src_b[(i * kSrcStride) + j] = (random() & 0xff);
    }
  }

  MaskCpuFlags(kCpuInitialized);
  double c_err, opt_err;

  c_err = CalcFrameSsim(src_a + kSrcStride * b + b, kSrcStride,
                        src_b + kSrcStride * b + b, kSrcStride,
                        kSrcWidth, kSrcHeight);

  MaskCpuFlags(-1);

  opt_err = CalcFrameSsim(src_a + kSrcStride * b + b, kSrcStride,
                          src_b + kSrcStride * b + b, kSrcStride,
                          kSrcWidth, kSrcHeight);

  EXPECT_EQ(opt_err, c_err);

  free_aligned_buffer_16(src_a)
  free_aligned_buffer_16(src_b)
}

}  // namespace libyuv
