using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Opc.UaFx;
using Opc.UaFx.Client;


namespace OpcUa_client
{
    public partial class Form1 : Form
    {

        #region Initialization
        private OpcClient _client;
        private Uri _uri;
        private OpcSubscription _subscription = null;
        private bool _subscribed = false;
        private readonly Dictionary<string, OpcNodeInfo> _nodes 
            = new Dictionary<string, OpcNodeInfo>(); //array for nodes (includes id and info)

        private readonly List<string> _dataTypes = new List<string>
        {
            "int16",
            "uint16",
            "int32",
            "uint32",
            "double",
            "string",
            "bool",
            "time"
        };
        #endregion

        #region Form

        public Form1()
        {
            InitializeComponent();
            comboBox1.DataSource = _dataTypes;
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Pen pen = new Pen(Color.DimGray);
            Graphics rectangle1 = e.Graphics;
            Graphics rectangle2 = e.Graphics;
            Graphics rectangle3 = e.Graphics;
            rectangle1.DrawRectangle(pen, 20, 70, 570, 70);
            rectangle2.DrawRectangle(pen, 20, 170, 570, 70);
            rectangle3.DrawRectangle(pen, 20, 268, 570, 70);
            rectangle1.Dispose();
            rectangle2.Dispose();
            rectangle3.Dispose();
        }
        #endregion

        #region Form functions

        //Method to connect to a server
        //URL must be given by a user
        //Connects to a server
        //If successful, enables buttons for other functions like reading or writing
        //Prints root node to a tree view
        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                _uri = new Uri(txtServerUri.Text);
                _client = new OpcClient(_uri, new OpcSecurityPolicy(OpcSecurityMode.None));

                _client.Connect();
                if (_client.State == OpcClientState.Connected)
                {
                    ButtonEnable();
                    btnConnect.Enabled = false;
                    btnDisconnect.Enabled = true;
                    WriteLogLine("Connected to: " + _uri);

                    // display the root node into tree view
                    var rootNode = _client.BrowseNode(OpcObjectTypes.ObjectsFolder);
                    _nodes[rootNode.NodeId.ToString()] = rootNode;
                    treeView1.Nodes.Add(rootNode.NodeId + " " + rootNode.Name);
                }
            }
            catch (Exception ex)
            {
                WriteLogLine(ex.Message);
            }
        }

        //Method to disconnect from server
        //When disconnected, enables rest of functions
        //Plus clear tree view
        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            try
            {
                StopMonitoring();
                _client.Disconnect();
                if (_client.State == OpcClientState.Disconnected)
                {
                    ButtonDisable();
                    btnDisconnect.Enabled = false;
                    btnConnect.Enabled = true;
                    WriteLogLine("Disconnected from: " + _uri);
                    lblReadValue.Text = "Read value:";
                    lblMonitoredValue.Text = "Monitored value";
                    treeView1.Nodes.Clear();
                }

            }
            catch (Exception ex)
            {
                WriteLogLine(ex.Message);
            }
        }

        //Method to read a value of chosen node
        private void btnReadNode_Click(object sender, EventArgs e)
        {
            try
            {
                lblReadValue.Text = "Read value: " + _client.ReadNode(txtNodeToRead.Text);
                WriteLogLine(lblReadValue.Text);
            }
            catch (Exception ex)
            {
                WriteLogLine(ex.Message);
            }
        }

        //Method to rewrite a node value
        //First, it converts data type to a chosen one
        //Then, it tries to rewrite the value
        private void btnWrite_Click(object sender, EventArgs e)
        {
            try
            {
                OpcStatus status = null;
                switch (comboBox1.Text)
                {
                    case "int16":
                        status = _client.WriteNode(txtNodeIdWrite.Text, Convert.ToInt16(txtNewValue.Text));
                        break;
                    case "uint16":
                        status = _client.WriteNode(txtNodeIdWrite.Text, Convert.ToUInt16(txtNewValue.Text));
                        break;
                    case "int32":
                        status = _client.WriteNode(txtNodeIdWrite.Text, Convert.ToInt32(txtNewValue.Text));
                        break;
                    case "uint32":
                        status = _client.WriteNode(txtNodeIdWrite.Text, Convert.ToUInt32(txtNewValue.Text));
                        break;
                    case "bool":
                        status = _client.WriteNode(txtNodeIdWrite.Text, Convert.ToBoolean(txtNewValue.Text));
                        break;
                    case "string":
                        status = _client.WriteNode(txtNodeIdWrite.Text, Convert.ToString(txtNewValue.Text));
                        break;
                    case "time":
                        status = _client.WriteNode(txtNodeIdWrite.Text, Convert.ToDateTime(txtNewValue.Text));
                        break;
                    case "double":
                        status = _client.WriteNode(txtNodeIdWrite.Text, Convert.ToDouble(txtNewValue.Text));
                        break;
                }

                if (status == null)
                {
                    WriteLogLine("Chosen wrong data type"); 
                }
                else
                {
                    if (status.IsGood)
                    {
                        WriteLogLine("Node " + txtNodeIdWrite.Text + " rewritten to " + txtNewValue.Text );
                    }
                    else
                    {
                        WriteLogLine("Failed to rewrite the value of " + txtNodeIdWrite.Text);
                    }
                }
            }
            catch (Exception ex)
            {
                WriteLogLine(ex.Message);
            }
        }

        //Method to monitor a selected node
        //Establishes a new subscription if needed, periodically reads a value of selected node
        //If subscription is valid, method unsubscribes it and stops monitoring 
        private void btnMonitor_Click(object sender, EventArgs e)
        {
            try
            {
                if (!_subscribed)
                {
                    StartMonitoring();
                }
                else
                {
                    StopMonitoring();
                }
            }
            catch (Exception ex)
            {
                WriteLogLine(ex.Message);
            }
        }

        //Tree view that prints found nodes
        private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
        {
            try
            {
                //gets nodeID of selected node in tree view
                string nodeId = treeView1.SelectedNode.Text.Split(' ').ToList().First();

                //pre-write nodeID to txt field "Read" and "Write"
                txtNodeToRead.Text = nodeId;
                txtNodeIdWrite.Text = nodeId;

                //finds child nodes
                //prints found nodes to tree view and adds them to _nodes
                foreach (var childNode in _nodes[nodeId].Children())
                {
                    treeView1.SelectedNode.Nodes.Add(childNode.NodeId + " " + childNode.Name);
                    _nodes[childNode.NodeId.ToString()] = childNode;
                }
            }
            catch (Exception ex)
            {
                WriteLogLine(ex.Message);
            }
            
        }
        #endregion

        #region Additional functions
        private void HandleDataChanged(object sender, OpcDataChangeReceivedEventArgs e)
        {
            OpcMonitoredItem item = (OpcMonitoredItem)sender;
            lblMonitoredValue.Text = "Monitored value: " + e.Item.Value;
        }

        private void ButtonEnable()
        {
            txtNodeToRead.Enabled = true;
            btnReadNode.Enabled = true;
            btnMonitor.Enabled = true;
            txtNodeIdWrite.Enabled = true;
            txtNewValue.Enabled = true;
            btnWrite.Enabled = true;
        }

        private void ButtonDisable()
        {
            txtNodeToRead.Enabled = false;
            txtNodeToRead.Text = "";
            btnReadNode.Enabled = false;
            btnMonitor.Enabled = false;
            txtNodeIdWrite.Enabled = false;
            txtNodeIdWrite.Text = "";
            txtNewValue.Enabled = false;
            txtNewValue.Text = "";
            btnWrite.Enabled = false;
        }

        private string Timestamp()
        {
            DateTime currentTime = DateTime.Now;
            return currentTime.ToString();
        }

        private void StopMonitoring()
        {
            if (_subscribed)
            {
                _subscription.Unsubscribe();
                _subscribed = false;
                btnMonitor.Text = "Monitor";
                lblMonitoredValue.Text = "Monitored value:";
                WriteLogLine("Node " + txtNodeToRead.Text + " monitoring has been stopped");
            }
        }

        private void StartMonitoring()
        {
            if (!_subscribed)
            {
                _subscription = _client.SubscribeDataChange(txtNodeToRead.Text, HandleDataChanged);
                _subscribed = true;
                btnMonitor.Text = "Stop";
                WriteLogLine( "Node " + txtNodeToRead.Text + " is being monitored" + txtNewValue.Text);

            }
        }

        private void WriteLogLine(string text)
        {
            txtInfoBox.Text += Timestamp() + " " + text + "\r\n";
        }

        private void txtServerUri_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter && txtServerUri.Text != "")
            {
                btnConnect_Click(null, null);
            }
        }
        #endregion
        
    }
}   
