using System;
using System.Diagnostics;
using System.Globalization;

// (c) Roger Hardiman 2016

// This class uses a System Timer to generate a YUV image at regular intervals
// The ReceivedYUVFrame event is fired for each new YUV image

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

        // Events that applications can receive
        public event ReceivedYUVFrameHandler ReceivedYUVFrame;

        // Delegated functions (essentially the function prototype)
        public delegate void ReceivedYUVFrameHandler(uint timestamp, int width, int height, byte[] data);


        // Local variables
        private System.Timers.Timer frame_timer;
        private int fps = 0;
        private Stopwatch stopwatch;
        private byte[] yuv_frame = null;
        private int x_position = 0;
        private int y_position = 0;
        private int width = 0;
        private int height = 0;
        private Object generate_lock = new Object();
        private long count = 0;

        // ASCII Font
        // Created by Roger Hardiman using an online generation tool
        // http://www.riyas.org/2013/12/online-led-matrix-font-generator-with.html

        byte[] ascii_0 = { 0x00, 0x3c, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c };
        byte[] ascii_1 = { 0x00, 0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x3e };
        byte[] ascii_2 = { 0x00, 0x3e, 0x42, 0x02, 0x0c, 0x30, 0x40, 0x7e };
        byte[] ascii_3 = { 0x00, 0x7c, 0x02, 0x02, 0x3c, 0x02, 0x02, 0x7c };
        byte[] ascii_4 = { 0x00, 0x0c, 0x14, 0x24, 0x44, 0x7e, 0x04, 0x04 };
        byte[] ascii_5 = { 0x00, 0x7e, 0x40, 0x40, 0x7c, 0x02, 0x02, 0x7c };
        byte[] ascii_6 = { 0x00, 0x3e, 0x40, 0x40, 0x7c, 0x42, 0x42, 0x3c };
        byte[] ascii_7 = { 0x00, 0x7e, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20 };
        byte[] ascii_8 = { 0x00, 0x3c, 0x42, 0x42, 0x3c, 0x42, 0x42, 0x3c };
        byte[] ascii_9 = { 0x00, 0x3c, 0x42, 0x42, 0x3c, 0x02, 0x02, 0x3e };
        byte[] ascii_colon = { 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00 };
        byte[] ascii_space = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
        byte[] ascii_dot = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00 };

        // Constructor
        public TestCard(int width, int height, int fps)
        {
            this.width = width;
            this.height = height;
            this.fps = fps;

            // YUV size
            int y_size = width * height;
            int u_size = (width >> 1) * (height >> 1);
            int v_size = (width >> 1) * (height >> 1);
            yuv_frame = new byte[y_size + u_size + v_size];

            // Set all values to 127
            for (int x = 0; x < yuv_frame.Length; x++)
            {
                yuv_frame[x] = 127;
            }

            stopwatch = new Stopwatch();
            stopwatch.Start();

            // Start timer. The Timer will generate each YUV frame
            frame_timer = new System.Timers.Timer();
            frame_timer.Interval = 1; // on first pass timer will fire straight away (cannot have zero interval)
            frame_timer.AutoReset = false; // do not restart timer after the time has elapsed
            frame_timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
            {
                // send a frame
                Send_YUV_Frame();
                count++;

                // Some CPU cycles will have been used in Sending the YUV Frame.
                // Compute the delay required (the Timer Interval) before sending the next YUV frame
                long time_for_next_tick_ms = (count * 1000) / fps;
                long time_to_wait = time_for_next_tick_ms - stopwatch.ElapsedMilliseconds;
                if (time_to_wait <= 0) time_to_wait = 1; // cannot have negative or zero intervals
                frame_timer.Interval = time_to_wait;
                frame_timer.Start();
            };
            frame_timer.Start();
        
        }

        // Dispose
        public void Disconnect()
        {
            // Stop the frame timer
            frame_timer.Stop();
            frame_timer.Dispose();
        }


        private void Send_YUV_Frame()
        {
            lock (generate_lock)
            {
                // Get the current time
                DateTime now_utc = DateTime.UtcNow;
                DateTime now_local = now_utc.ToLocalTime();


                long timestamp_ms = ((long)(now_utc.Ticks / TimeSpan.TicksPerMillisecond));

                // Generate the String to write
                char[] overlay = null;

                if (width >= 96)
                {
                    // Need 12 characters of 8x8 pixels. 12*8 = 96
                    // HH:MM:SS.mmm
                    String overlay_str = now_local.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture); // do not replace : or . by local formats
                    overlay = overlay_str.ToCharArray();
                }
                else
                {
                    // Min for most video formats is 16x16, enough for 2 characters
                    String overlay_str = now_local.ToString("ss", CultureInfo.InvariantCulture); // do not replace : or . by local formats
                    overlay = overlay_str.ToCharArray();
                }

                // process each character
                int start_row = ((height / 2) - 4); // start 4 pixels above the centre row (4 is half the font height)
                for (int c = 0; c < overlay.Length; c++)
                {
                    byte[] font = ascii_space;
                    if (overlay[c] == '0') font = ascii_0;
                    if (overlay[c] == '1') font = ascii_1;
                    if (overlay[c] == '2') font = ascii_2;
                    if (overlay[c] == '3') font = ascii_3;
                    if (overlay[c] == '4') font = ascii_4;
                    if (overlay[c] == '5') font = ascii_5;
                    if (overlay[c] == '6') font = ascii_6;
                    if (overlay[c] == '7') font = ascii_7;
                    if (overlay[c] == '8') font = ascii_8;
                    if (overlay[c] == '9') font = ascii_9;
                    if (overlay[c] == ' ') font = ascii_space;
                    if (overlay[c] == ':') font = ascii_colon;
                    if (overlay[c] == '.') font = ascii_dot;

                    // process the font character
                    for (int rows = 0; rows < 8; rows++)
                    {
                        int y_plane_pos = (start_row * width) + (rows * width) + (c * 8);
                        byte row_byte = font[rows];
                        // bit shift the row byte into individual pixels where the font On/Off maps to Y intensity 50 or 200
                        for (int bits = 0; bits < 8; bits++)
                        {
                            if ((row_byte & 0x80) == 0x80)
                            {
                                // Pixel On
                                yuv_frame[y_plane_pos] = 200;
                            }
                            else
                            {
                                yuv_frame[y_plane_pos] = 50;
                            }
                            y_plane_pos++;
                            row_byte = (byte)(row_byte << 1); // shift up so the next 'bit' to process is the most significant bit
                        }
                    }
                }




                // Toggle the pixel value
                byte pixel_value = yuv_frame[(y_position * width) + x_position];

                // change brightness of pixel			
                if (pixel_value > 128) pixel_value = 30;
                else pixel_value = 230;

                yuv_frame[(y_position * width) + x_position] = pixel_value;

                // move the x and y position
                x_position = x_position + 5;
                if (x_position >= width)
                {
                    x_position = 0;
                    y_position = y_position + 1;
                }

                if (y_position >= height)
                {
                    y_position = 0;
                }

                // fire the Event
                if (ReceivedYUVFrame != null)
                {
                    ReceivedYUVFrame((uint)stopwatch.ElapsedMilliseconds, width, height, yuv_frame);
                }
            }
        }

    }
}
