/* 
 *  Mini C# sample for PCAN-Ethernet Gateways
 *  using the RAW CAN Socket Interface on UDP
 * 
 *  (c)2014 PEAK-System Technik GmbH
 *  info@peak-system.com  
 *  www.peak-system.com
 *  Autor: u.w.
 *  
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;


namespace CANUDPReceiver
{

    /// <summary>
    /// Simple implementation of the UdpState class mentioned on 
    /// http://msdn.microsoft.com/en-us/library/c8s04db1(v=VS.80).aspx
    /// </summary>
    internal class UdpState
    {
        internal UdpState(UdpClient c, IPEndPoint e)
        {
            this.c = c;
            this.e = e;
        }

        internal UdpClient c;
        internal IPEndPoint e;
    }

    class Program
    {
        public static bool messageReceived = false;
        
        // A CAN message
        public struct TCANMsgUDP
        {
            /// <summary>
            /// 11/29-bit message identifier
            /// </summary>
            public int ID;
            /// <summary>
            /// Type of the message
            /// </summary>
            public byte MSGTYPE;
            /// <summary>
            /// Data Length Code of the message (0..8)
            /// </summary>
            public byte LEN;
            /// <summary>
            /// Data of the message (DATA[0]..DATA[7])
            /// </summary>
            public byte[] DATA;
        }

        /// <summary>
        /// Represents a timestamp of a received PCAN message.
        /// Total Microseconds = micros + 1000 * millis + 0xFFFFFFFF * 1000 * millis_overflow
        /// </summary>
        public struct TCANTimestampUDP
        {
            /// <summary>
            /// Base-value: milliseconds: 0.. 2^32-1
            /// </summary>
            public uint millis;
            /// <summary>
            /// Roll-arounds of millis
            /// </summary>
            public ushort millis_overflow;
            /// <summary>
            /// Microseconds: 0..999
            /// </summary>
            public ushort micros;
        }

        public static TCANMsgUDP CANFrame = new TCANMsgUDP();
        public static TCANTimestampUDP CANTime = new TCANTimestampUDP();
        
        public static bool runforever = true;
        public static UInt64 CANFrameCounter = 0;
         
        static void Main(string[] args)
        {
            int localPort = 51001; // Default value 
            UdpState state;
            UdpClient client;
            IPAddress tempAddress;

            IPEndPoint remoteSender = new IPEndPoint(IPAddress.Any, 0);

            CANFrame.DATA = new byte[8];

            bool flag = false;
            
            for( int i = 0; i < args.Length; i++)
            {
                string cmd = args[i];
                string value;
                int tempInt;
                
                
                switch (cmd)
                {
                    case "-lp":
                        value = GetValue(args, ref i);
                        if (int.TryParse(value, out tempInt))
                            localPort = tempInt;
                        break;

                    case "-rh":
                        value = GetValue(args, ref i);
                        if (IPAddress.TryParse(value, out tempAddress))
                            remoteSender.Address = tempAddress;
                        else if (int.TryParse(value, out tempInt) && tempInt == 0)
                            remoteSender.Address = IPAddress.Any;
                        break;

                    case "-?":
                    default:
                        PrintHelpText();
                        flag = true;
                        break;
                }
            }

            // Exit application after help text is displayed
            if (flag)
                return;

            // Display some information
            Console.WriteLine("PEAK-System, PCAN-Ethernet Gateway UDP CAN Receive Sample.");
            Console.WriteLine("Local PORT for CAN: " + localPort);
            Console.WriteLine("Remote Host IP: " + remoteSender.Address.ToString());
            Console.WriteLine("Use '-?' to display help.");
            Console.WriteLine("Press CTRL-C to quit.\n");


            // Create UDP client CAN1
            client = new UdpClient(localPort);
            state = new UdpState(client, remoteSender);

            // Wait for receiving CAN ID 0x100 to terminate application
            do
            {
                // Start async receiving
                client.BeginReceive(new AsyncCallback(DataReceived), state);
                while (!messageReceived)
                {
                    ;  //do whatever you think is usefull to do here...
                    Thread.Sleep(1);
                   
                }
                messageReceived = false;
            } while (runforever);

            Console.WriteLine("Received CAN ID 0x100 - Stop now!");
            Console.Write("CAN-Msg COUNTER: " + CANFrameCounter);
            client.Close();
            Console.ReadKey();
        }


        /* This function will be called AFTER the client.BeginReceive(..) is finished
         */ 
        private static void DataReceived(IAsyncResult ar)
        {
            
            UdpClient c = (UdpClient)((UdpState)ar.AsyncState).c;
            IPEndPoint wantedIpEndPoint = (IPEndPoint)((UdpState)(ar.AsyncState)).e;
            IPEndPoint receivedIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
            Byte[] receiveBytes = c.EndReceive(ar, ref receivedIpEndPoint);
            
            // Check sender
            bool isRightHost = (wantedIpEndPoint.Address.Equals(receivedIpEndPoint.Address)) || wantedIpEndPoint.Address.Equals(IPAddress.Any);
            if (isRightHost) // && isRightPort)
            {
                // Convert data to ASCII and print in console
                // string receivedText = ASCIIEncoding.ASCII.GetString(receiveBytes);
                // Console.Write(receivedText);

                // Dcode RAW Data Bytes to CAN Msg Structure incl. TimeStamp from Source
                DecodeCANPacket(receiveBytes);
                // Print Time to Console
                Console.WriteLine(CANTime.millis + ":" + CANTime.micros);
                // Print CAN Data to Console
                Console.Write("ID: " + CANFrame.ID + " MsgType: " + CANFrame.MSGTYPE + " DLC: " + CANFrame.LEN + " Data: ");
                for (int count = 0; count < CANFrame.LEN; count++)
                    Console.Write(CANFrame.DATA[count] + " ");
                Console.Write("\n\n");
                // internal CAN Frame Counter
                CANFrameCounter++;
                // Stop when id 0x100 was received
                if (CANFrame.ID == 256)
                    runforever = false;// 0x100 -> STOP
            }
            else
            {
                Console.Write("IP is wrong...\n");
                Console.Write("IP: " +  receivedIpEndPoint.Address + "\n");
            }
            messageReceived = true;
        }

        /* Print HELP
         */
        private static void PrintHelpText()
        {
            Console.WriteLine("CANUDPReceiver print incoming CAN data to screen.");
            Console.WriteLine("Command switches:");
            Console.WriteLine("-? : Displays this text.");
            Console.WriteLine("-lp : Set local receiving PORT for CAN. \"-lp 4001\" Default: 51001");
            Console.WriteLine("-rh : Set remote host IP. \"-rh 192.168.1.10\" Default: 0 (Any IP)");
            Console.WriteLine("\n Example of usage:\nCANoverEthernet.exe -lp 67002 -rh 192.168.1.203 ");
        }

        /* Function to decode the raw DATA into CAN and TimeStam Struct
         */
        private static void DecodeCANPacket(Byte[] packet)
        {
            if (packet != null) {
                CANFrame.ID = (packet[24] & 0x1f) << 24 | (packet[25] & 0xff) << 16 | (packet[26] & 0xff) << 8 | (packet[27] & 0xff);
                CANFrame.MSGTYPE = ((byte) (packet[24] >> 6));
                CANFrame.LEN = packet[21];
                if (CANFrame.LEN > 0)
                    for (int datalen = 0; datalen < CANFrame.LEN; datalen++)
                        CANFrame.DATA[datalen] = packet[datalen + 28];
                long timel = ((packet[12] & 0xff) << 24 | (packet[13] & 0xff) << 16 | (packet[14] & 0xff) << 8 | (packet[15] & 0xff));
                long timeh = ((packet[16] & 0xff) << 24 | (packet[17] & 0xff) << 16 | (packet[18] & 0xff) << 8 | (packet[19] & 0xff));
                long time = ((timeh & 0xffffffff) << 32);
                time |= (timel & 0xffffffffL);
                long millies = time / 1000;
                CANTime.micros = ((ushort) (time - millies * 1000));
                CANTime.millis = ((uint) (millies & 0xffffffff));
                CANTime.millis_overflow = ((ushort) (millies >> 32));
                return;
            }
        }

        /* Read ARGS
        */
        private static string GetValue(string[] args, ref int i)
        {
            string value = String.Empty;
            if (args.Length >= i + 2)
            {
                i++;
                value = args[i];
            }
            return value;
        }

    }
}
