/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#elif defined XP_WIN
#include <winsock2.h>
#endif
#include <string.h>

#include "nspr.h"
#include "YuvStamper.h"

typedef uint32_t UINT4; //Needed for r_crc32() call
extern "C" {
#include "r_crc32.h"
}

namespace mozilla {

#define ON_5 0x20
#define ON_4 0x10
#define ON_3 0x08
#define ON_2 0x04
#define ON_1 0x02
#define ON_0 0x01

/*
  0, 0, 1, 1, 0, 0,
  0, 1, 0, 0, 1, 0,
  1, 0, 0, 0, 0, 1,
  1, 0, 0, 0, 0, 1,
  1, 0, 0, 0, 0, 1,
  0, 1, 0, 0, 1, 0,
  0, 0, 1, 1, 0, 0
*/
static unsigned char DIGIT_0 [] =
  { ON_3 | ON_2,
    ON_4 | ON_1,
    ON_5 | ON_0,
    ON_5 | ON_0,
    ON_5 | ON_0,
    ON_4 | ON_1,
    ON_3 | ON_2
  };
    
/*
  0, 0, 0, 1, 0, 0,
  0, 0, 0, 1, 0, 0,
  0, 0, 0, 1, 0, 0,
  0, 0, 0, 1, 0, 0,
  0, 0, 0, 1, 0, 0,
  0, 0, 0, 1, 0, 0,
  0, 0, 0, 1, 0, 0,
*/
static unsigned char DIGIT_1 [] =
  { ON_2,
    ON_2,
    ON_2,
    ON_2,
    ON_2,
    ON_2,
    ON_2
  };

/*
  1, 1, 1, 1, 1, 0,
  0, 0, 0, 0, 0, 1,
  0, 0, 0, 0, 0, 1,
  0, 1, 1, 1, 1, 0,
  1, 0, 0, 0, 0, 0,
  1, 0, 0, 0, 0, 0,
  0, 1, 1, 1, 1, 1,
*/
static unsigned char DIGIT_2 [] =
  { ON_5 | ON_4 | ON_3 | ON_2 | ON_1,
    ON_0,
    ON_0,
    ON_4 | ON_3 | ON_2 | ON_1,
    ON_5,
    ON_5,
    ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
  };

/*
  1, 1, 1, 1, 1, 0,
  0, 0, 0, 0, 0, 1,
  0, 0, 0, 0, 0, 1,
  0, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 1,
  0, 0, 0, 0, 0, 1,
  1, 1, 1, 1, 1, 0,
*/
static unsigned char DIGIT_3 [] =
  { ON_5 | ON_4 | ON_3 | ON_2 | ON_1,
    ON_0,
    ON_0,
    ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
    ON_0,
    ON_0,
    ON_5 | ON_4 | ON_3 | ON_2 | ON_1,
  };

/*
  0, 1, 0, 0, 0, 1,
  0, 1, 0, 0, 0, 1,
  0, 1, 0, 0, 0, 1,
  0, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 1,
  0, 0, 0, 0, 0, 1,
  0, 0, 0, 0, 0, 1
*/
static unsigned char DIGIT_4 [] =
  { ON_4 | ON_0,
    ON_4 | ON_0,
    ON_4 | ON_0,
    ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
    ON_0,
    ON_0,
    ON_0,
  };

/*
  0, 1, 1, 1, 1, 1,
  1, 0, 0, 0, 0, 0,
  1, 0, 0, 0, 0, 0,
  0, 1, 1, 1, 1, 0,
  0, 0, 0, 0, 0, 1,
  0, 0, 0, 0, 0, 1,
  1, 1, 1, 1, 1, 0,
*/
static unsigned char DIGIT_5 [] =
  { ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
    ON_5,
    ON_5,
    ON_4 | ON_3 | ON_2 | ON_1,
    ON_0,
    ON_0,
    ON_5 | ON_4 | ON_3 | ON_2 | ON_1,
  };

/*
  0, 1, 1, 1, 1, 1,
  1, 0, 0, 0, 0, 0,
  1, 0, 0, 0, 0, 0,
  1, 1, 1, 1, 1, 0,
  1, 0, 0, 0, 0, 1,
  1, 0, 0, 0, 0, 1,
  0, 1, 1, 1, 1, 0,
*/
static unsigned char DIGIT_6 [] =
  { ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
    ON_5,
    ON_5,
    ON_4 | ON_3 | ON_2 | ON_1,
    ON_5 | ON_0,
    ON_5 | ON_0,
    ON_4 | ON_3 | ON_2 | ON_1,
  };

/*
  1, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 1,
  0, 0, 0, 0, 1, 0,
  0, 0, 0, 1, 0, 0,
  0, 0, 1, 0, 0, 0,
  0, 1, 0, 0, 0, 0,
  1, 0, 0, 0, 0, 0
*/
static unsigned char DIGIT_7 [] =
  { ON_5 | ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
    ON_0,
    ON_1,
    ON_2,
    ON_3,
    ON_4,
    ON_5
  };

/*
  0, 1, 1, 1, 1, 1,
  1, 0, 0, 0, 0, 1,
  1, 0, 0, 0, 0, 1,
  0, 1, 1, 1, 1, 0,
  1, 0, 0, 0, 0, 1,
  1, 0, 0, 0, 0, 1,
  0, 1, 1, 1, 1, 0
*/
static unsigned char DIGIT_8 [] =
  { ON_4 | ON_3 | ON_2 | ON_1,
    ON_5 | ON_0,
    ON_5 | ON_0,
    ON_4 | ON_3 | ON_2 | ON_1,
    ON_5 | ON_0,
    ON_5 | ON_0,
    ON_4 | ON_3 | ON_2 | ON_1,
  };

/*
  0, 1, 1, 1, 1, 1,
  1, 0, 0, 0, 0, 1,
  1, 0, 0, 0, 0, 1,
  0, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 1,
  0, 0, 0, 0, 0, 1,
  0, 1, 1, 1, 1, 0
*/
static unsigned char DIGIT_9 [] =
  { ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
    ON_5 | ON_0,
    ON_5 | ON_0,
    ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
    ON_0,
    ON_0,
    ON_4 | ON_3 | ON_2 | ON_1,
  };

static unsigned char *DIGITS[] = {
    DIGIT_0,
    DIGIT_1,
    DIGIT_2,
    DIGIT_3,
    DIGIT_4,
    DIGIT_5,
    DIGIT_6,
    DIGIT_7,
    DIGIT_8,
    DIGIT_9
};

  YuvStamper::YuvStamper(unsigned char* pYData,
			 uint32_t width,
			 uint32_t height,
			 uint32_t stride,
			 uint32_t x,
			 uint32_t y,
			 unsigned char symbol_width,
			 unsigned char symbol_height):
    pYData(pYData), mStride(stride),
    mWidth(width), mHeight(height),
    mSymbolWidth(symbol_width), mSymbolHeight(symbol_height),
    mCursor(x, y) {}

  bool YuvStamper::Encode(uint32_t width, uint32_t height, uint32_t stride,
			  unsigned char* pYData, unsigned char* pMsg, size_t msg_len,
			  uint32_t x, uint32_t y)
  {
    YuvStamper stamper(pYData, width, height, stride,
		       x, y, sBitSize, sBitSize);

    // Reserve space for a checksum.
    if (stamper.Capacity() < 8 * (msg_len + sizeof(uint32_t)))
    {
      return false;
    }

    bool ok = false;
    uint32_t crc;
    unsigned char* pCrc = reinterpret_cast<unsigned char*>(&crc);
    r_crc32(reinterpret_cast<char*>(pMsg), (int)msg_len, &crc);
    crc = htonl(crc);

    while (msg_len-- > 0) {
      if (!stamper.Write8(*pMsg++)) {
	return false;
      }
    }

    // Add checksum after the message.
    ok = stamper.Write8(*pCrc++) &&
         stamper.Write8(*pCrc++) &&
         stamper.Write8(*pCrc++) &&
         stamper.Write8(*pCrc++);

    return ok;
  }

  bool YuvStamper::Decode(uint32_t width, uint32_t height, uint32_t stride,
			  unsigned char* pYData, unsigned char* pMsg, size_t msg_len,
			  uint32_t x, uint32_t y)
  {
    YuvStamper stamper(pYData, width, height, stride,
		       x, y, sBitSize, sBitSize);

    unsigned char* ptr = pMsg;
    size_t len = msg_len;
    uint32_t crc, msg_crc;
    unsigned char* pCrc = reinterpret_cast<unsigned char*>(&crc);

    // Account for space reserved for the checksum
    if (stamper.Capacity() < 8 * (len + sizeof(uint32_t))) {
      return false;
    }

    while (len-- > 0) {
      if(!stamper.Read8(*ptr++)) {
	return false;
      }
    }

    if (!(stamper.Read8(*pCrc++) &&
          stamper.Read8(*pCrc++) &&
          stamper.Read8(*pCrc++) &&
          stamper.Read8(*pCrc++))) {
      return false;
    }

    r_crc32(reinterpret_cast<char*>(pMsg), (int)msg_len, &msg_crc);
    return crc == htonl(msg_crc);
  }

  inline uint32_t YuvStamper::Capacity()
  {
    // Enforce at least a symbol width and height offset from outer edges.
    if (mCursor.y + mSymbolHeight > mHeight) {
      return 0;
    }

    if (mCursor.x + mSymbolWidth > mWidth && !AdvanceCursor()) {
      return 0;
    }

    // Normalize frame integral to mSymbolWidth x mSymbolHeight
    uint32_t width = mWidth / mSymbolWidth;
    uint32_t height = mHeight / mSymbolHeight;
    uint32_t x = mCursor.x / mSymbolWidth;
    uint32_t y = mCursor.y / mSymbolHeight;

    return (width * height - width * y)- x;
  }

  bool YuvStamper::Write8(unsigned char value)
  {
    // Encode MSB to LSB.
    unsigned char mask = 0x80;
    while (mask) {
      if (!WriteBit(!!(value & mask))) {
	return false;
      }
      mask >>= 1;
    }
    return true;
  }

  bool YuvStamper::WriteBit(bool one)
  {
    // A bit is mapped to a mSymbolWidth x mSymbolHeight square of luma data points.
    // Don't use ternary op.: https://bugzilla.mozilla.org/show_bug.cgi?id=1001708
    unsigned char value;
    if (one)
      value = sYOn;
    else
      value = sYOff;

    for (uint32_t y = 0; y < mSymbolHeight; y++) {
      for (uint32_t x = 0; x < mSymbolWidth; x++) {
	*(pYData + (mCursor.x + x) + ((mCursor.y + y) * mStride)) = value;
      }
    }

    return AdvanceCursor();
  }

  bool YuvStamper::AdvanceCursor()
  {
    mCursor.x += mSymbolWidth;
    if (mCursor.x + mSymbolWidth > mWidth) {
      // move to the start of the next row if possible.
      mCursor.y += mSymbolHeight;
      if (mCursor.y + mSymbolHeight > mHeight) {
	// end of frame, do not advance
	mCursor.y -= mSymbolHeight;
	mCursor.x -= mSymbolWidth;
	return false;
      } else {
	mCursor.x = 0;
      }
    }

    return true;
  }

  bool YuvStamper::Read8(unsigned char &value)
  {
    unsigned char octet = 0;
    unsigned char bit = 0;

    for (int i = 8; i > 0; --i) {
      if (!ReadBit(bit)) {
	return false;
      }
      octet <<= 1;
      octet |= bit;
    }

    value = octet;
    return true;
  }

  bool YuvStamper::ReadBit(unsigned char &bit)
  {
    uint32_t sum = 0;
    for (uint32_t y = 0; y < mSymbolHeight; y++) {
      for (uint32_t x = 0; x < mSymbolWidth; x++) {
	sum += *(pYData + mStride * (mCursor.y + y) + mCursor.x + x);
      }
    }

    // apply threshold to collected bit square
    bit = (sum > (sBitThreshold * mSymbolWidth * mSymbolHeight)) ? 1 : 0;
    return AdvanceCursor();
  }

  bool YuvStamper::WriteDigits(uint32_t value)
  {
    char buf[20];
    PR_snprintf(buf, sizeof(buf), "%.5u", value);
    size_t size = strlen(buf);

    if (Capacity() < size) {
      return false;
    }

    for (size_t i=0; i < size; ++i) {
      if (!WriteDigit(buf[i] - '0'))
	return false;
      if (!AdvanceCursor()) {
	return false;
      }
    }

    return true;
  }

  bool YuvStamper::WriteDigit(unsigned char digit) {
    if (digit > sizeof(DIGITS)/sizeof(DIGITS[0]))
      return false;

    unsigned char *dig = DIGITS[digit];
    for (uint32_t row = 0; row < sDigitHeight; ++row) {
      unsigned char mask = 0x01 << (sDigitWidth - 1);
      for (uint32_t col = 0; col < sDigitWidth; ++col, mask >>= 1) {
	if (dig[row] & mask) {
	  for (uint32_t xx=0; xx < sPixelSize; ++xx) {
	    for (uint32_t yy=0; yy < sPixelSize; ++yy) {
	      WritePixel(pYData,
			 mCursor.x + (col * sPixelSize) + xx,
			 mCursor.y + (row * sPixelSize) + yy);
	    }
	  }
	}
      }
    }

    return true;
  }

  void YuvStamper::WritePixel(unsigned char *data, uint32_t x, uint32_t y) {
    unsigned char *ptr = &data[y * mStride + x];
    // Don't use ternary op.: https://bugzilla.mozilla.org/show_bug.cgi?id=1001708
    if (*ptr > sLumaThreshold)
      *ptr = sLumaMin;
    else
      *ptr = sLumaMax;
   }

}  // Namespace mozilla.
