/*
 * libjingle
 * Copyright 2004--2014 Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "talk/media/base/yuvframegenerator.h"

#include <string.h>
#include <sstream>

#include "webrtc/base/basictypes.h"
#include "webrtc/base/common.h"

namespace cricket {

// These values were figured out by trial and error. If you change any
// basic parameters e.g. unit-bar size or bars-x-offset, you may need to change
// background-width/background-height.
const int kBarcodeBackgroundWidth = 160;
const int kBarcodeBackgroundHeight = 100;
const int kBarsXOffset = 12;
const int kBarsYOffset = 4;
const int kUnitBarSize = 2;
const int kBarcodeNormalBarHeight = 80;
const int kBarcodeGuardBarHeight = 96;
const int kBarcodeMaxEncodableDigits = 7;

YuvFrameGenerator::YuvFrameGenerator(int width, int height,
                                     bool enable_barcode) {
  width_ = width;
  height_ = height;
  frame_index_ = 0;
  int size = width_ * height_;
  int qsize = size / 4;
  frame_data_size_ = size + 2 * qsize;
  y_data_ = new uint8_t[size];
  u_data_ = new uint8_t[qsize];
  v_data_ = new uint8_t[qsize];
  if (enable_barcode) {
    ASSERT(width_ >= kBarcodeBackgroundWidth);
    ASSERT(height_>= kBarcodeBackgroundHeight);
    barcode_start_x_ = 0;
    barcode_start_y_ = height_ - kBarcodeBackgroundHeight;
  } else {
    barcode_start_x_ = -1;
    barcode_start_y_ = -1;
  }
}

YuvFrameGenerator::~YuvFrameGenerator() {
  delete y_data_;
  delete u_data_;
  delete v_data_;
}

void YuvFrameGenerator::GenerateNextFrame(uint8_t* frame_buffer,
                                          int32_t barcode_value) {
  int size = width_ * height_;
  int qsize = size / 4;
  memset(y_data_, 0, size);
  memset(u_data_, 0, qsize);
  memset(v_data_, 0, qsize);

  DrawLandscape(y_data_, width_, height_);
  DrawGradientX(u_data_, width_/2, height_/2);
  DrawGradientY(v_data_, width_/2, height_/2);
  DrawMovingLineX(u_data_, width_/2, height_/2, frame_index_);
  DrawMovingLineY(v_data_, width_/2, height_/2, frame_index_);
  DrawBouncingCube(y_data_, width_, height_, frame_index_);

  if (barcode_value >= 0) {
      ASSERT(barcode_start_x_ != -1);
      DrawBarcode(barcode_value);
  }

  memcpy(frame_buffer, y_data_, size);
  frame_buffer += size;
  memcpy(frame_buffer, u_data_, qsize);
  frame_buffer += qsize;
  memcpy(frame_buffer, v_data_, qsize);

  frame_index_ = (frame_index_ + 1) & 0x0000FFFF;
}

void YuvFrameGenerator::DrawLandscape(uint8_t* p, int w, int h) {
  int x, y;
  for (y = 0; y < h; y++) {
    for (x = 0; x < w; x++) {
      p[x + y * w] = x % (y+1);
      if (((x > w / 2 - (w / 32)) && (x < w / 2 + (w / 32))) ||
          ((y > h / 2 - (h / 32)) && (y < h / 2 + (h / 32)))) {
        p[x + y * w] = (((x + y) / 8 % 2)) ? 255 : 0;
      }
    }
  }
}

void YuvFrameGenerator::DrawGradientX(uint8_t* p, int w, int h) {
  int x, y;
  for (y = 0; y < h; y++) {
    for (x = 0; x < w; x++) {
      p[x + y * w] = (x << 8) / w;
    }
  }
}

void YuvFrameGenerator::DrawGradientY(uint8_t* p, int w, int h) {
  int x, y;
  for (y = 0; y < h; y++) {
    for (x = 0; x < w; x++) {
      p[x + y * w] = (y << 8) / h;
    }
  }
}

void YuvFrameGenerator::DrawMovingLineX(uint8_t* p, int w, int h, int n) {
  int x, y;
  x = n % (w * 2);
  if (x >= w) x = w + w - x - 1;
  for (y = 0; y < h; y++) {
    p[x + y * w] = 255;
  }
}

void YuvFrameGenerator::DrawMovingLineY(uint8_t* p, int w, int h, int n) {
  int x, y;
  y = n % (h * 2);
  if (y >= h) y = h + h - y - 1;
  for (x = 0; x < w; x++) {
    p[x + y * w] = 255;
  }
}

void YuvFrameGenerator::DrawBouncingCube(uint8_t* p, int w, int h, int n) {
  int x, y, pw, ph, px, py;
  pw = w / 16;
  ph = h / 16;
  px = n % (w * 2);
  py = n % (h * 2);
  if (px >= w) px = w + w - px - 1;
  if (py >= h) py = h + h - py - 1;
  for (y = py - ph; y < py + ph; y++) {
    if (y >=0 && y < h) {
      for (x = px - pw; x < px + pw; x++) {
        if (x >= 0 && x < w) {
          p[x + y * w] = 255;
        }
      }
    }
  }
}

void YuvFrameGenerator::GetBarcodeBounds(int* top, int* left,
                                         int* width, int* height) {
  ASSERT(barcode_start_x_ != -1);
  *top = barcode_start_y_;
  *left = barcode_start_x_;
  *width = kBarcodeBackgroundWidth;
  *height = kBarcodeBackgroundHeight;
}

static void ComputeBarcodeDigits(uint32_t value, std::stringstream* result) {
  // Serialize |value| as 7-char string, padded with 0's to the left.
  result->width(kBarcodeMaxEncodableDigits);
  result->fill('0');
  *result << value;

  // Compute check-digit and append to result. Steps described here:
  // http://en.wikipedia.org/wiki/European_Article_Number#Calculation_of_checksum_digit
  int sum = 0;
  for (int pos = 1; pos <= kBarcodeMaxEncodableDigits; pos++) {
    char next_char;
    result->get(next_char);
    uint8_t digit = next_char - '0';
    sum += digit * (pos % 2 ? 3 : 1);
  }
  uint8_t check_digit = sum % 10;
  if (check_digit != 0) {
    check_digit = 10 - check_digit;
  }

  *result << static_cast<int>(check_digit);
  result->seekg(0);
}

void YuvFrameGenerator::DrawBarcode(uint32_t value) {
  std::stringstream value_str_stream;
  ComputeBarcodeDigits(value, &value_str_stream);

  // Draw white filled rectangle as background to barcode.
  DrawBlockRectangle(y_data_, barcode_start_x_, barcode_start_y_,
                     kBarcodeBackgroundWidth, kBarcodeBackgroundHeight,
                     width_, 255);
  DrawBlockRectangle(u_data_, barcode_start_x_ / 2, barcode_start_y_ / 2,
                     kBarcodeBackgroundWidth / 2, kBarcodeBackgroundHeight / 2,
                     width_ / 2, 128);
  DrawBlockRectangle(v_data_, barcode_start_x_ / 2, barcode_start_y_ / 2,
                     kBarcodeBackgroundWidth / 2, kBarcodeBackgroundHeight / 2,
                     width_ / 2, 128);

  // Scan through chars (digits) and draw black bars.
  int x = barcode_start_x_ + kBarsXOffset;
  int y = barcode_start_y_ + kBarsYOffset;
  int pos = 0;
  x = DrawSideGuardBars(x, y, kBarcodeGuardBarHeight);
  while (true) {
    char next_char;
    value_str_stream.get(next_char);
    if (!value_str_stream.good()) {
      break;
    }
    if (pos++ == 4) {
      x = DrawMiddleGuardBars(x, y, kBarcodeGuardBarHeight);
    }
    uint8_t digit = next_char - '0';
    x = DrawEanEncodedDigit(digit, x, y, kBarcodeNormalBarHeight, pos > 4);
  }
  x = DrawSideGuardBars(x, y, kBarcodeGuardBarHeight);
}

int YuvFrameGenerator::DrawMiddleGuardBars(int x, int y, int height) {
  x += kUnitBarSize;
  DrawBlockRectangle(y_data_, x, y, kUnitBarSize, height, width_, 0);
  x += (kUnitBarSize * 2);
  DrawBlockRectangle(y_data_, x, y, kUnitBarSize, height, width_, 0);
  return x + (kUnitBarSize * 2);
}

int YuvFrameGenerator::DrawSideGuardBars(int x, int y, int height) {
  DrawBlockRectangle(y_data_, x, y, kUnitBarSize, height, width_, 0);
  x += (kUnitBarSize * 2);
  DrawBlockRectangle(y_data_, x, y, kUnitBarSize, height, width_, 0);
  return x + kUnitBarSize;
}

// For each digit: 0-9, |kEanEncodings| contains a bit-mask indicating
// which bars are black (1) and which are blank (0). These are for the L-code
// only. R-code values are bitwise negation of these. Reference:
// http://en.wikipedia.org/wiki/European_Article_Number#Binary_encoding_of_data_digits_into_EAN-13_barcode // NOLINT
const uint8_t kEanEncodings[] = {13, 25, 19, 61, 35, 49, 47, 59, 55, 11};

int YuvFrameGenerator::DrawEanEncodedDigit(int digit, int x, int y,
                                           int height, bool flip) {
  uint8_t ean_encoding = kEanEncodings[digit];
  if (flip) {
    ean_encoding = ~ean_encoding;
  }
  uint8_t mask = 0x40;
  for (int i = 6; i >= 0; i--, mask >>= 1) {
    if (ean_encoding & mask) {
      DrawBlockRectangle(y_data_, x, y, kUnitBarSize, height, width_, 0);
    }
    x += kUnitBarSize;
  }
  return x;
}

void YuvFrameGenerator::DrawBlockRectangle(uint8_t* p,
                                           int x_start,
                                           int y_start,
                                           int width,
                                           int height,
                                           int pitch,
                                           uint8_t value) {
  for (int x = x_start; x < x_start + width; x++) {
    for (int y = y_start; y < y_start + height; y++) {
      p[x + y * pitch] = value;
    }
  }
}

}  // namespace cricket
