#region "Legal Notice"
/*******************************************************************************
 *
 * Copyright (c) 2008 Alien Technology Corporation.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1)	Redistributions of source code must retain the above copyright 
 *		notice, this list of conditions and the following disclaimer. 
 *
 * 2)	Redistributions in binary form must reproduce the above copyright 
 *		notice, this list of conditions and the following disclaimer 
 *		in the documentation and/or other materials provided with the distribution. 
 *
 * 3)	Neither the name of Alien Technology Corporation nor the names of any 
 *		contributors may be used to endorse or promote products derived from this 
 *		software without specific prior written permission. 
 *
 *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 *	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *	ARE DISCLAIMED. IN NO EVENT SHALL ALIEN TECHNOLOGY CORPORATION OR ITS 
 *	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 *	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 *	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 *	OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 *	WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 *	OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 *	OF THE POSSIBILITY OF SUCH DAMAGE.
 ***********************************************************************************/
#endregion


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Threading;

using ZedGraph;
using nsAlienRFID2;


namespace Example_13___ITR_Singulation
{
    public partial class Form1 : Form
    {
        private delegate void manageTag();
        private delegate void manageList();

        const string USERNAME = "alien";
        const string PASSWORD = "password";
        const string CUSTOM_FORMAT = "id:%k t:${MSEC2} TIME2:${TIME2} a:%a vel:${SPEED} sig:${RSSI} freq:${FREQ}";

        const int TAG_STREAM_PORT = 7798;
        const int COMMAND_PORT = 23;

        const double X_SCALE_MAX = 20.0;

        static int siCurvePointsCnt = 200;      // make it the same as the nudPointsCnt.Value at design time
        static bool sbDisposing = false;

        CAlienServer mTagStreamServer = new CAlienServer(TAG_STREAM_PORT);
        string msTagStreamAddress = null;

        static readonly object moStreamMsgLock = new object();

        #region "My COLORS"
        Color[] mColors = new Color[] { 
            Color.Green,
            Color.Purple,
            Color.OrangeRed,
            Color.SteelBlue,
            Color.DeepPink,
            Color.DarkSlateGray,
            Color.DodgerBlue,
            Color.MediumVioletRed,
            Color.Blue,
            Color.Teal,
            Color.Crimson,
            Color.SeaGreen,
            Color.Indigo,
            Color.SaddleBrown,
            Color.MidnightBlue,
            Color.DarkViolet,
            Color.DarkSlateBlue
        };
        #endregion
        int miNextColor = 0;


        Dictionary<string, DataPoint> mdDataPoints = new Dictionary<string, DataPoint>();
        static readonly object moDataPointsLock = new object();

        Dictionary<string, string> mdLastHex = new Dictionary<string, string>();
        static readonly object moLastHexLock = new object();

        double mStartTime = 0;

        static bool mbSmoothing = true;
        static bool mbDiffusionCorrection = true;
        static bool mbRSSIWeighting = false;
        static bool mbASCIIMode = true;

        bool mbListRefreshNeeded = false;

        public Form1()
        {
            InitializeComponent();
        }
        // Set the size and location of the ZedGraphControl
        private void SetSize()
        {
            // Control is always 10 pixels inset from the client rectangle of the form
            Rectangle formRect = this.ClientRectangle;
            formRect.Inflate(-10, -10);

            if (zedGraphControl1.Size != formRect.Size)
            {
                zedGraphControl1.Location = formRect.Location;
                zedGraphControl1.Size = formRect.Size;
            }
        }

        private void zg1_Resize(object sender, EventArgs e)
        {
            //SetSize();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Thread.CurrentThread.Name = "Alien-Ex.13: ITR - Singulation";
            this.Text = "Alien Library .NET - ITR Singulation sample application";

            string exePath = Application.ExecutablePath;
            int idx = exePath.LastIndexOf("\\");
            if (idx != -1)
                AlienLog.Path = exePath.Substring(0, idx + 1);
            AlienLog.WriteLine(true, "Starting the 'Ex13-ITR Singulation' application.");


            mTagStreamServer = new CAlienServer(TAG_STREAM_PORT);
            mTagStreamServer.Logging = true;

            mTagStreamServer.ServerMessageReceived += new CAlienServer.ServerMessageReceivedEventHandler(OnTagRead);
            msTagStreamAddress = mTagStreamServer.NotificationHost;
            chkListening.Checked = true;

            GraphPane myPane = zedGraphControl1.GraphPane;
            myPane.Title.Text = "Position v.Time";
            myPane.XAxis.Title.Text = "Time, Seconds";
            myPane.YAxis.Title.Text = "Relative Position, Meters";

            // Just manually control the X axis range so it scrolls continuously
            // instead of discrete step-sized jumps
            myPane.XAxis.Scale.Min = 0;
            myPane.XAxis.Scale.Max = X_SCALE_MAX;
            myPane.XAxis.Scale.MinorStep = 5;
            myPane.XAxis.Scale.MajorStep = 10;

            myPane.YAxis.Scale.MinorStep = 0.25;
            myPane.YAxis.Scale.MajorStep = 0.5;
            myPane.YAxis.Scale.MaxAuto = true;
            myPane.YAxis.Scale.MinAuto = true;

            // Scale the axes
            zedGraphControl1.AxisChange();
            txtIPAddress.Text = "Enter reader's IP Address";
            txtIPAddress.Select();
            txtIPAddress.Focus();
        }

        string extractField(string target, string fieldName)
        {
            int fieldpos = 0;
            int startpos = 0;
            int endpos = 0;
            string result = "";

            fieldpos = target.IndexOf(fieldName);
            if (fieldpos != -1)
            {
                startpos = fieldpos + fieldName.Length;
                endpos = target.IndexOf(" ", startpos);
                if (endpos != -1)
                    result = target.Substring(startpos, endpos - startpos);
                else
                    result = target.Substring(startpos);
            }
            return result;
        }

        void OnTagRead(string msg)
        {
            lock (moStreamMsgLock)
            {
                if (sbDisposing || !this.Created) return;
                if (msg == null) return;

                string[] tags = msg.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                string tag = msg.Trim();

                DataPoint datapoint = null;

                for (int i = 0; i < tags.Length; i++)
                {
                    string id = extractField(tag, "id:");
                    if (string.IsNullOrEmpty(id))
                        return;
                    string sT = extractField(tag, "t:");
                    double t = 0;
                    if (!double.TryParse(sT, out t))
                    {
                        AlienLog.WriteLine(true, "Bad Time token '" + sT + "' in tag: " + tag);
                        return;
                    }
                    string sVel = extractField(tag, "vel:");
                    double vel = 0;
                    if (!double.TryParse(sVel, out vel))
                    {
                        AlienLog.WriteLine(true, "Bad Velocity token '" + sVel + "' in tag: " + tag);
                        return;
                    }
                    string sRSSI = extractField(tag, "sig:");
                    double rssi = 0;
                    if (!double.TryParse(sRSSI, out rssi))
                    {
                        AlienLog.WriteLine(true, "Bad Signal token '" + sRSSI + "' in tag: " + tag);
                        return;
                    }
                    string sFreq = extractField(tag, "freq:");
                    double freq = 0;
                    if (!double.TryParse(sFreq, out freq))
                    {
                        AlienLog.WriteLine(true, "Bad Frequency token '" + sFreq + "' in tag: " + tag);
                        return;
                    }
                    string sTime = extractField(tag, "TIME2:");

                    lock (moDataPointsLock)
                    {
                        if (!mdDataPoints.ContainsKey(id))
                        {
                            datapoint = new DataPoint();
                            datapoint.Color = mColors[miNextColor];
                            miNextColor++;
                            if (miNextColor > mColors.Length - 1)
                                miNextColor = 0;
                        }
                        else
                            datapoint = mdDataPoints[id];
                        if (!string.IsNullOrEmpty(sTime))
                            datapoint.sTime = sTime;

                        datapoint.id = id;
                        if (mStartTime == 0)
                            mStartTime = t;
                        datapoint.t = (t - mStartTime) / 1000;
                        datapoint.Vel = -vel;
                        datapoint.Freq = freq;
                        datapoint.Sig = rssi;

                        double filt = -5.0;
                        double dt = datapoint.t - datapoint.lastT;

                        datapoint.smoothVel = datapoint.smoothVel * Math.Exp(filt * dt) +
                                              datapoint.Vel * (1 - Math.Exp(filt * dt));

                        datapoint.integVel = datapoint.integVel +
                                             2 * datapoint.lastVel * dt * Math.Exp(filt * dt);

                        if (mbDiffusionCorrection)
                            datapoint.integVel = datapoint.integVel * Math.Exp(-0.15 * dt);

                        if (mbASCIIMode)
                        {
                            byte b = byte.Parse(id.Substring(id.Length - 2), System.Globalization.NumberStyles.HexNumber);
                            datapoint.Caption = Convert.ToChar(b).ToString();
                        }
                        updateGUI(datapoint);

                        datapoint.lastT = datapoint.t;
                        if (datapoint.Freq != datapoint.lastFreq)
                            datapoint.sigOffset += datapoint.Sig - datapoint.lastSig;

                        datapoint.smoothSig = datapoint.smoothSig * Math.Exp(filt * dt) + (datapoint.Sig - datapoint.sigOffset) * (1 - Math.Exp(filt * dt));

                        if (mbSmoothing)
                            datapoint.integVelSig = datapoint.integVel * datapoint.smoothSig;
                        else
                            datapoint.integVelSig = datapoint.integVel * (datapoint.Sig - datapoint.sigOffset);

                        if (datapoint.integVelSig > datapoint.integVelSigMax)
                        {
                            datapoint.integVelSigMax = datapoint.integVelSig;
                            datapoint.tintegVelSigMax = datapoint.t;
                            mbListRefreshNeeded = true;
                        }
                        datapoint.lastFreq = datapoint.Freq;
                        datapoint.lastSig = datapoint.Sig;
                        datapoint.lastVel = mbSmoothing ? datapoint.smoothVel : datapoint.Vel;
                        if (datapoint.integVel > datapoint.integVelMax)
                        {
                            datapoint.integVelMax = datapoint.integVel;
                            datapoint.tIntegVelMax = datapoint.t;
                        }
                        else
                            mbListRefreshNeeded = true;

                        if (!mdDataPoints.ContainsKey(id))
                            mdDataPoints.Add(id, datapoint);
                        else
                            mdDataPoints[id] = datapoint;
                    }
                }
                if (mbListRefreshNeeded)
                    refreshList();
            }
        }

        private void updateGUI(DataPoint datapoint)
        {
            try
            {
                //lock (this)       should not be needed
                //{
                    string id = datapoint.id;

                    manageTag method = delegate
                    {
                        alienTagControl1.Speed = datapoint.smoothVel;
                        if (datapoint.sTime != null)
                            alienTagControl1.Time = datapoint.sTime;
                        alienTagControl1.RSSI = datapoint.Sig;
                        alienTagControl1.Frequency = (float)datapoint.Freq;
                        alienTagControl1.TagID = datapoint.id;

                        double x = datapoint.t;
                        double y = (mbRSSIWeighting) ? datapoint.integVelSig : datapoint.integVel;

                        CurveList curveList = zedGraphControl1.GraphPane.CurveList;
                        if ((curveList == null) || (curveList.IndexOf(id) == -1))
                        {
                            // The RollingPointPairList is an efficient storage class that always
                            // keeps a rolling set of point data without needing to shift any data values
                            RollingPointPairList list = new RollingPointPairList(siCurvePointsCnt);
                            list.Add(x, y);
                            zedGraphControl1.GraphPane.AddCurve(id, list, datapoint.Color);
                        }
                        else
                        {
                            IPointListEdit pointList = curveList[id].Points as IPointListEdit;
                            if (pointList != null)
                                pointList.Add(x, y);
                            else
                            {
                                AlienLog.WriteLine(true, "pointList is null for id: " + id);
                                return;
                            }
                        }

                        if (mbASCIIMode)
                        {
                            lock (moLastHexLock)
                            {
                                if (!mdLastHex.ContainsKey(id))
                                {
                                    mdLastHex.Add(id, datapoint.Caption);
                                    if ((datapoint.lastVel > 0) && (datapoint.Vel < 0))
                                        lblASCIIdemo.Text += datapoint.Caption;
                                }
                            }

                        }
                        adjustXScalse(x);

                        if (zedGraphControl1.GraphPane.CurveList.Count == 1)
                        {
                            if (datapoint.smoothVel > 0.1)
                            {
                                lblMinus.Visible = false;
                                lblPlus.Visible = true;
                            }
                            else if (datapoint.smoothVel < -0.1)
                            {
                                lblMinus.Visible = true;
                                lblPlus.Visible = false;
                            }
                            else
                            {
                                lblMinus.Visible = false;
                                lblPlus.Visible = false;
                            }
                        }
                        else
                        {
                            lblMinus.Visible = false;
                            lblPlus.Visible = false;
                        }
                    };
                    this.BeginInvoke(method);
                //}
            }
            catch (Exception ex)
            { AlienLog.WriteLine(true, "updateGUI() caught: " + ex.Message); }
        }
        private void refreshList()
        {
            System.Collections.ArrayList al = new System.Collections.ArrayList();
            string txt = "";
            foreach (DataPoint dp in mdDataPoints.Values)
            {
                string s = null;
                try
                {
                    if (mbASCIIMode)
                    {
                        s = Convert.ToChar(byte.Parse(dp.id.Substring(dp.id.Length - 2), System.Globalization.NumberStyles.HexNumber)).ToString();
                        txt += s;
                    }
                    al.Add(dp.id);

                    //if (mbRSSIWeighting)
                    //{
                    //    if ((dp.tintegVelSigMax != 0) && (dp.tintegVelSigMax > dp.integVelSig))
                    //        al.Add(String.Format(dp.tintegVelSigMax.ToString(), "00.000") + "\t" + dp.id);
                    //}
                    //else if ((dp.tIntegVelMax != 0) && (dp.integVelMax > dp.integVel))
                    //    al.Add(string.Format(dp.tIntegVelMax.ToString(), "00.000") + "\t" + dp.id);
                }
                catch (Exception ex) { AlienLog.WriteLine(true, "Exception caught refreshing list: " + ex.Message); }
            }
            manageList method = delegate
            {
                object[] items = al.ToArray();
                lstTags.Items.Clear();
                if (items != null)
                    lstTags.Items.AddRange(items);
                lstTags.Refresh();
                if (mbASCIIMode)
                {
                    lblASCIIdemo.Text = txt;
                }
            };
            if (this.Created)
                this.BeginInvoke(method);
        }
        private void adjustXScalse(double x)
        {
            Scale xScale = zedGraphControl1.GraphPane.XAxis.Scale;
            //Scale yScale = zedGraphControl1.GraphPane.Y2Axis.Scale;

            if (x == 0)
            {
                xScale.Min = 0;
                xScale.Max = X_SCALE_MAX;
            }
            else if (x > xScale.Max - xScale.MajorStep)
            {
                xScale.Max = x + xScale.MajorStep;
                xScale.Min = xScale.Max - X_SCALE_MAX;
            }

            zedGraphControl1.AxisChange();
            zedGraphControl1.Invalidate();

        }
        private bool verifyIPAddress(string ip)
        {
            IPAddress ipAddress = null;
            if (!IPAddress.TryParse(ip, out ipAddress))
                return false;

            return (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
        }
        private void btnConfigure_Click(object sender, EventArgs e)
        {
            if (!verifyIPAddress(txtIPAddress.Text.Trim()))
            {
                errorProvider1.SetError(txtIPAddress, "Not a valid IPAddress.  Must be not empty string in IPv4 format.");
            }
            else
            {
                btnConfigure.Enabled = false;
                errorProvider1.Clear();
                this.Cursor = Cursors.WaitCursor;

                clsReader reader = new clsReader(true);
                try
                {
                    
                    reader.ConnectAndLogin(txtIPAddress.Text.Trim(), COMMAND_PORT, USERNAME, PASSWORD);
                    if (!reader.IsConnected)
                    {
                        lblStatus.Text = "Connection failed.";
                        this.Cursor = Cursors.Default;
                        return;
                    }
                    reader.AutoMode = "OFF";
                    reader.SpeedFilter = "0";   // if ITR is not supported by reader throws here
                    reader.RSSIFilter = "0";
                    reader.TagType = 16;
                    reader.AutoAction = "Acquire";
                    reader.AutoStartTrigger = "0 0";
                    reader.AcqG2Q = "2";
                    reader.AcqG2Select = "1";
                    reader.AcqG2Session = "2";
                    reader.AcqG2Cycles = "1";
                    reader.AcqG2Count = "1";
                    reader.AcqG2Mask = getMask();
                    reader.RFAttenuation = 0;
                    reader.NotifyMode = "OFF";
                    reader.TagListMillis = "ON";
                    reader.TagStreamAddress = msTagStreamAddress;
                    reader.TagStreamFormat = "Custom";
                    reader.TagStreamCustomFormat = CUSTOM_FORMAT;
                    reader.TagStreamMode = "ON";
                    reader.RFModulation = "HS"; // "06M4";
                    reader.ClearTagList();
                    string antSeq = getAntennaSequence();
                    if (string.IsNullOrEmpty(antSeq))
                        throw new Exception("At least one antenna must be enabled.");
                    reader.AntennaSequence = antSeq;
                    if (!chkListening.Checked)
                    {
                        chkListening.Checked = true;    // this will start AutoMode as well
                    }
                    else
                        reader.AutoMode = "ON";

                    lblStatus.Text = "Reader configured OK.";
                }
                catch (Exception ex)
                {
                    if (ex.Message.Contains("Command not understood"))
                        lblStatus.Text = "This reader doesn't support ITR.";
                    else 
                        lblStatus.Text = ex.Message;
                }
                reader.Dispose();
                this.Cursor = Cursors.Default;
                btnConfigure.Enabled = true;
            }
        }
        
        string getAntennaSequence()
        {
            string antSeq = "";
            if (chk0.Checked)
                antSeq += "0, ";
            if (chk1.Checked)
                antSeq += "1, ";
            if (chk2.Checked)
                antSeq += "2, ";
            if (chk3.Checked)
                antSeq += "3, ";
            if (antSeq.Length > 1)
                return antSeq.Substring(0, antSeq.Length - 2);
            else
                return null;
        }
        string getMask()
        {
            string mask = "0";
            StringBuilder sb = new StringBuilder();

            string s = txtMask.Text.Trim().Replace(" ", "");
            if (string.IsNullOrEmpty(s))
                return mask;
            if ((s.Length % 2) != 0)
                s = "0" + s;
            int i = 0;
            for (i = 0; i < s.Length - 1; i = i + 2)
            {
                byte b = 0x00;
                string ss = s.Substring(i, 2);
                if (!byte.TryParse(ss, System.Globalization.NumberStyles.HexNumber, null, out b))
                    throw new Exception("Invalid Mask byte: " + ss);

                sb.Append(" " + ss);
            }
            s = sb.ToString();
            txtMask.Text = s;
            mask = "1, 32, " + ((int)((i)/2) * 8).ToString() + ", " + s;
            return mask;
        }

        private void chkSmoothing_CheckedChanged(object sender, EventArgs e)
        {
            mbSmoothing = (sender as CheckBox).Checked;
        }

        private void chkDiffusionCorrection_CheckedChanged(object sender, EventArgs e)
        {
            mbDiffusionCorrection = (sender as CheckBox).Checked;
        }

        private void chkRSSIWeighting_CheckedChanged(object sender, EventArgs e)
        {
            mbRSSIWeighting = (sender as CheckBox).Checked;
        }

        private void chkASCIIMode_CheckedChanged(object sender, EventArgs e)
        {
            bool state = (sender as CheckBox).Checked;
            mbASCIIMode = state;
            lblASCIIdemo.Visible = state;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            sbDisposing = true;
            try
            {
                setAutoMode("OFF");
            }
            catch { }
            if (mTagStreamServer.IsListening)
                mTagStreamServer.StopListening();
        }

        private void chkListening_CheckedChanged(object sender, EventArgs e)
        {
            try
            {
                bool state = chkListening.Checked;
                if (state)
                {
                    mTagStreamServer.StartListening();
                    setAutoMode("ON");
                }
                else
                {
                    setAutoMode("OFF");
                    mTagStreamServer.StopListening();
                }
            }
            catch (Exception ex)
            {
                lblStatus.Text = ex.Message;
            }
        }
        private void setAutoMode(string state)
        {
            string ip = txtIPAddress.Text.Trim();
            if (!string.IsNullOrEmpty(ip))
            {
                clsReader reader = new clsReader(true);
                reader.ConnectAndLogin(ip, COMMAND_PORT, USERNAME, PASSWORD);
                reader.AutoMode = state;
            }
        }

        private void nudVelocityScale_ValueChanged(object sender, EventArgs e)
        {
            alienTagControl1.SpeedScale = (double)nudVelocityScale.Value;
        }

        private void nudRSSIScale_ValueChanged(object sender, EventArgs e)
        {
            alienTagControl1.RSSILimit = (double)nudRSSIScale.Value;
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            lock (moDataPointsLock)
            {
                mStartTime = 0;
                miNextColor = 0;
                zedGraphControl1.GraphPane.CurveList.Clear();
                adjustXScalse(0);
                lblASCIIdemo.Text = "";
                zedGraphControl1.Invalidate();
                mdDataPoints.Clear();
                lstTags.Items.Clear();
                alienTagControl1.RSSI = 0;
                alienTagControl1.Speed = 0;
                lock (moLastHexLock)
                {
                    mdLastHex.Clear();
                }
            }
        }

        private void nudPointsCnt_ValueChanged(object sender, EventArgs e)
        {
            siCurvePointsCnt = (int)nudPointsCnt.Value;
        }
    }
}