using System;
using System.Collections.Generic;
using System.Linq;


// Tiny H264 Encoder
// World's Smallest h.264 Encoder, by Ben Mesander.
// https://cardinalpeak.com/blog/worlds-smallest-h-264-encoder/
//
// Ported to C# by Roger Hardiman www.rjh.org.uk

// Input: YUV image that must be 128x96
// Output: H264 NAL
//
// This is a very simple lossless H264 encoder. No compression is used and so the output NAL data is as
// large as the input YUV data.
// It is used for a quick example of H264 encoding in pure .Net without needing OS specific APIs
// or cross compiled C libraries.
//
// The H264 SPS/PPS data includes the image size. As the SPS/PPS is hard coded in this example the YUV
// image size must be 128 x 96

namespace Inspectron.HawkEye.RTSP.Server
{
    public class TinyH264Encoder
    {

        int width = 0;
        int height = 0;
        int uv_width = 0;
        int uv_height = 0;
        int y_size = 0;
        int u_size = 0;
        int v_size = 0;

        byte[] sps = { 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2 };
        //byte[] sps_b = { 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2 }; // Annex B
        //byte[] sps32 = { 0x00, 0x00, 0x00, 0x07, 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2 }; // 32 bit size

        byte[] pps = { 0x68, 0xce, 0x38, 0x80 };
        //byte[] pps_b = { 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80 }; // Annex B
        //byte[] pps32 = { 0x00, 0x00, 0x00, 0x04, 0x68, 0xce, 0x38, 0x80 }; // 32 bit size

        byte[] slice_header = { 0x05, 0x88, 0x84, 0x21, 0xa0 };
        //byte[] slice_header_b = { 0x00, 0x00, 0x00, 0x01, 0x05, 0x88, 0x84, 0x21, 0xa0 };
        //byte[] slice_header_32 = { 0x00, 0x00, 0x00, 0x00, 0x05, 0x88, 0x84, 0x21, 0xa0 }; // must replace size bytes
        byte[] slice_end = { 0x80 };
        byte[] macroblock_header = { 0x0d, 0x00 };

        List<byte> nal = new List<byte>();

        // Constuctor
        public TinyH264Encoder()
        {
            this.width = 128;  // Hard coded size that is embedded in the SPS/PPS data
            this.height = 96;  // Hard coded size that is embedded in the SPS/PPS data
            this.uv_width = width >> 1;
            this.uv_height = height >> 1;
            this.y_size = width * height;
            this.u_size = (width >> 1) * (height >> 1);
            this.v_size = (width >> 1) * (height >> 1);
        }

        public byte[] GetRawSPS()
        {
            return sps.ToArray();
        }

        public byte[] GetRawPPS()
        {
            return pps.ToArray();
        }

        public byte[] CompressFrame(byte[] yuv_data)
        {
            // we can only do 128 x 96
            if (width != 128) return null;
            if (height != 96) return null;

            // check size
            if (yuv_data.Length < (y_size + u_size + v_size))
            {
                // the yuv image is too small.
                return null;
            }

            nal.Clear();

            // Slice Header
            foreach (byte b in slice_header) nal.Add(b);

            // Add each macro block
            for (int i = 0; i < (height / 16); i++) {
                for (int j = 0; j < (width / 16); j++) {
                    macroblock(i, j, yuv_data);
                }
            }

            // Add slice end
            foreach (byte b in slice_end) nal.Add(b);

            byte[] nal_array = nal.ToArray();

            return nal_array;
        }

        /* Write a macroblock's worth of YUV data in I_PCM mode */
        private void macroblock(int i, int j, byte[] frame)
        {
            int x, y;

            if (!((i == 0) && (j == 0)))
            {
                foreach (byte b in macroblock_header) nal.Add(b);
            }

            for (x = i * 16; x < ((i + 1) * 16); x++)
            {
                for (y = j * 16; y < ((j + 1) * 16); y++)
                {
                    nal.Add(frame[(x * width) + y]);
                }
            }
            for (x = i * 8; x < (i + 1) * 8; x++)
            {
                for (y = j * 8; y < (j + 1) * 8; y++)
                {
                    nal.Add(frame[y_size + (x * uv_width) + y]);
                }
            }
            for (x = i * 8; x < (i + 1) * 8; x++)
            {
                for (y = j * 8; y < (j + 1) * 8; y++)
                {
                    nal.Add(frame[y_size + u_size + (x * uv_width) + y]);
                }
            }
        }


        public void ChangeAnnexBto32BitSize(byte[] data)
        {

            if (data.Length < 4) return;

            // change data from 0x00 0x00 0x00 0x01 format to 32 bit size
            int len = data.Length - 4;// subtract Annex B header size

            if (BitConverter.IsLittleEndian)
            {
                data[0] = (byte)((len >> 24) & 0xFF);
                data[1] = (byte)((len >> 16) & 0xFF);
                data[2] = (byte)((len >> 8) & 0xFF);
                data[3] = (byte)((len << 0) & 0xFF);
            }
            else
            {
                data[0] = (byte)((len >> 0) & 0xFF);
                data[1] = (byte)((len >> 8) & 0xFF);
                data[2] = (byte)((len >> 16) & 0xFF);
                data[3] = (byte)((len >> 24) & 0xFF);
            }
        }
    }
}

