// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Routines for encoding and decoding a small number of bits into an image
// in a way that is decodable even after scaling/encoding/cropping.
//
// The encoding is very simple:
//
//   ####    ####    ########    ####        ####    ####
//   ####    ####    ########    ####        ####    ####
//   ####    ####    ########    ####        ####    ####
//   ####    ####    ########    ####        ####    ####
//   1   2   3   4   5   6   7   8   9   10  11  12  13  14
//   <-----start----><--one-bit-><-zero bit-><----stop---->
//
// We use a basic unit, depicted here as four characters wide.
// We start with 1u black 1u white 1u black 1u white. (1-4 above)
// From there on, a "one" bit is encoded as 2u black and 1u white,
// and a zero bit is encoded as 1u black and 2u white. After
// all the bits we end the pattern with the same pattern as the
// start of the pattern.

#include <deque>
#include <vector>

#include "base/logging.h"
#include "media/base/video_frame.h"
#include "media/cast/test/utility/barcode.h"

namespace media {
namespace cast {
namespace test {

const int kBlackThreshold = 256 * 2 / 3;
const int kWhiteThreshold = 256 / 3;

bool EncodeBarcode(const std::vector<bool>& bits,
                   scoped_refptr<VideoFrame> output_frame) {
  DCHECK(output_frame->format() == VideoFrame::YV12 ||
         output_frame->format() == VideoFrame::YV16 ||
         output_frame->format() == VideoFrame::I420 ||
         output_frame->format() == VideoFrame::YV12J);
  int row_bytes = output_frame->row_bytes(VideoFrame::kYPlane);
  std::vector<unsigned char> bytes(row_bytes);
  for (int i = 0; i < row_bytes; i++) {
    bytes[i] = 255;
  }
  size_t units = bits.size() * 3 + 7;  // White or black bar where size matters.
  // We only use 60% of the image to make sure it works even if
  // the image gets cropped.
  size_t unit_size = row_bytes * 6 / 10 / units;
  if (unit_size < 1) return false;
  size_t bytes_required = unit_size * units;
  size_t padding = (row_bytes - bytes_required) / 2;
  unsigned char *pos = &bytes[padding];
  // Two leading black bars.
  memset(pos, 0, unit_size);
  pos += unit_size * 2;
  memset(pos, 0, unit_size);
  pos += unit_size * 2;
  for (size_t bit = 0; bit < bits.size(); bit++) {
    memset(pos, 0, bits[bit] ? unit_size * 2: unit_size);
    pos += unit_size * 3;
  }
  memset(pos, 0, unit_size);
  pos += unit_size * 2;
  memset(pos, 0, unit_size);
  pos += unit_size;
  DCHECK_LE(pos - &bytes.front(), row_bytes);

  // Now replicate this one row into all rows in kYPlane.
  for (int row = 0; row < output_frame->rows(VideoFrame::kYPlane); row++) {
    memcpy(output_frame->data(VideoFrame::kYPlane) +
           output_frame->stride(VideoFrame::kYPlane) * row,
           &bytes.front(),
           row_bytes);
  }
  return true;
}

namespace {
bool DecodeBarCodeRows(const scoped_refptr<VideoFrame>& frame,
                       std::vector<bool>* output,
                       int min_row,
                       int max_row) {
  // Do a basic run-length encoding
  std::deque<int> runs;
  bool is_black = true;
  int length = 0;
  for (int pos = 0; pos < frame->row_bytes(VideoFrame::kYPlane); pos++) {
    float value = 0.0;
    for (int row = min_row; row < max_row; row++) {
      value += frame->data(VideoFrame::kYPlane)[
          frame->stride(VideoFrame::kYPlane) * row + pos];
    }
    value /= max_row - min_row;
    if (is_black ? value > kBlackThreshold : value < kWhiteThreshold) {
      is_black = !is_black;
      runs.push_back(length);
      length = 1;
    } else {
      length++;
    }
  }
  runs.push_back(length);

  // Try decoding starting at each white-black transition.
  while (runs.size() >=  output->size() * 2 + 7) {
    std::deque<int>::const_iterator i = runs.begin();
    double unit_size = (i[1] + i[2] + i[3] + i[4]) / 4;
    bool valid = true;
    if (i[0] > unit_size * 2 || i[0] < unit_size / 2) valid = false;
    if (i[1] > unit_size * 2 || i[1] < unit_size / 2) valid = false;
    if (i[2] > unit_size * 2 || i[2] < unit_size / 2) valid = false;
    if (i[3] > unit_size * 2 || i[3] < unit_size / 2) valid = false;
    i += 4;
    for (size_t bit = 0; valid && bit < output->size(); bit++) {
      if (i[0] > unit_size / 2 && i[0] <= unit_size * 1.5 &&
          i[1] > unit_size * 1.5 && i[1] <= unit_size * 3) {
        (*output)[bit] = false;
      } else if (i[1] > unit_size / 2 && i[1] <= unit_size * 1.5 &&
                 i[0] > unit_size * 1.5 && i[0] <= unit_size * 3) {
        (*output)[bit] = true;
      } else {
        // Not a valid code
        valid = false;
      }
      i += 2;
    }
    if (i[0] > unit_size * 2 || i[0] < unit_size / 2) valid = false;
    if (i[1] > unit_size * 2 || i[1] < unit_size / 2) valid = false;
    if (i[2] > unit_size * 2 || i[2] < unit_size / 2) valid = false;
    i += 3;
    DCHECK(i <= runs.end());
    if (valid) {
      // Decoding successful, return true
     return true;
    }
    runs.pop_front();
    runs.pop_front();
  }
  return false;
}

}  // namespace

// Note that "output" is assumed to be the right size already. This
// could be inferred from the data, but the decoding is more robust
// if we can assume that we know how many bits we want.
bool DecodeBarcode(const scoped_refptr<VideoFrame>& frame,
                   std::vector<bool>* output) {
  DCHECK(frame->format() == VideoFrame::YV12 ||
         frame->format() == VideoFrame::YV16 ||
         frame->format() == VideoFrame::I420 ||
         frame->format() == VideoFrame::YV12J);
  int rows = frame->rows(VideoFrame::kYPlane);
  // Middle 10 lines
  if (DecodeBarCodeRows(frame,
                        output,
                        std::max(0, rows / 2 - 5),
                        std::min(rows, rows / 2 + 5))) {
    return true;
  }

  // Top 5 lines
  if (DecodeBarCodeRows(frame, output, 0, std::min(5, rows))) {
    return true;
  }

  return false;
}

}  // namespace test
}  // namespace cast
}  // namespace media