/*
 *  server_example_access_control.cs
 *
 *  - How to use access control mechanisms
 *  - How to implement RBAC features based on access control mechanisms
 */

using System;
using IEC61850.Server;
using IEC61850.Common;
using System.Threading;
using System.Net;
using static IEC61850.Server.IedServer;
using System.Collections.Generic;
using System.Reflection.Metadata;
using IEC61850.Client;
using ReportControlBlock = IEC61850.Server.ReportControlBlock;
using IEC61850.Model;
using System.Data.Common;

namespace server_access_control
{
    class MainClass
    {
        class PTOC1Settings
        {
            public float strVal;
            public int opDlTmms;
            public int rsDlTmms;
            public int rstTms;
        }

        public static void Main(string[] args)
        {
            bool running = true;

            /* run until Ctrl-C is pressed */
            Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;
                running = false;
            };

            /* Create new server configuration object */
            IedServerConfig config = new IedServerConfig();

            /* Set buffer size for buffered report control blocks to 200000 bytes */
            config.ReportBufferSize = 200000;

            /* Set stack compliance to a specific edition of the standard (WARNING: data model has also to be checked for compliance) */
           config.Edition = Iec61850Edition.EDITION_2;

            /* Set the base path for the MMS file services */
            config.FileServiceBasePath = "./vmd-filestore/";

            /* disable MMS file service */
            config.FileServiceEnabled =  false;

            /* enable dynamic data set service */
            config.DynamicDataSetServiceEnabled = true;

            /* disable log service */
            config.LogServiceEnabled = false;

            /* set maximum number of clients */
            config.MaxMmsConnections = 2;

            IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile("model.cfg");

            IedServer iedServer = new IedServer(iedModel, config);

            iedServer.SetServerIdentity("libiec61850.com", "access control example", "1.0.0");

            DataObject spcso1 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.SPCSO1");

            iedServer.SetControlHandler(spcso1, delegate (ControlAction action, object parameter, MmsValue value, bool test)
            {
                if (test)
                    return ControlHandlerResult.FAILED;

                if (value.GetType() == MmsType.MMS_BOOLEAN)
                {
                    Console.WriteLine("received binary control command: ");

                    if (value.GetBoolean())
                        Console.WriteLine("on\n");
                    else
                        Console.WriteLine("off\n");
                }
                else
                    return ControlHandlerResult.FAILED;

                return ControlHandlerResult.OK;
            }, spcso1);

            void ConnectionCallBack(IedServer server, ClientConnection clientConnection, bool connected, object parameter)
            {
                if (connected)
                    Console.WriteLine("Connection opened\n");
                else
                    Console.WriteLine("Connection closed\n");
            }

            var connectionCallBack = new ConnectionIndicationHandler(ConnectionCallBack);
            iedServer.SetConnectionIndicationHandler(connectionCallBack, "127.0.0.1");


            /* Install handler to log RCB events */

            iedServer.SetRCBEventHandler(delegate (object parameter, ReportControlBlock rcb, ClientConnection con, RCBEventType eventType, string parameterName, MmsDataAccessError serviceError)
            {
                Console.WriteLine("RCB: " + rcb.Parent.GetObjectReference() + "." + rcb.Name + " event: " + eventType.ToString());

                if (con != null)
                {
                    Console.WriteLine("  caused by client " + con.GetPeerAddress());
                }
                else
                {
                    Console.WriteLine("  client = null");
                }

                if ((eventType == RCBEventType.SET_PARAMETER) || (eventType == RCBEventType.GET_PARAMETER))
                {
                    Console.WriteLine("RCB: "+rcb.Name + " event: "+ eventType .ToString()+ "\n");
                    Console.WriteLine("  param:  "+ parameterName + "\n");
                    Console.WriteLine("  result: "+ serviceError.ToString() + "\n");
                }

                if (eventType == RCBEventType.ENABLED)
                {
                    Console.WriteLine("RCB: "+ rcb.Name + " event: " + eventType.ToString() + "\n");
                    string rptId = rcb.RptID;
                    Console.WriteLine("   rptID:  "+ rptId+"\n");
                    string dataSet =rcb.DataSet;
                    Console.WriteLine("   datSet:"+ dataSet+"\n");
                }


             }, null);

            /* Install handler to control access to control blocks (RCBs, LCBs, GoCBs, SVCBs, SGCBs)*/
            bool ControlBlockAccessCallBack(object parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice ld, LogicalNode ln, string objectName, string subObjectName, ControlBlockAccessType accessType)
            {
                Console.WriteLine(acsiClass.ToString() + " "+ accessType.ToString() + " access " +  ld.GetName() + ln.GetName() +"/"+ objectName + "." + subObjectName + "\n");

                return true;
            }

            iedServer.SetControlBlockAccessHandler(ControlBlockAccessCallBack, iedServer);

            /* By default access to variables with FC=DC and FC=CF is not allowed.
            * This allow to write to simpleIOGenericIO/GGIO1.NamPlt.vendor variable used
            * by iec61850_client_example1.
            */
            iedServer.SetWriteAccessPolicy(FunctionalConstraint.DC, AccessPolicy.ACCESS_POLICY_ALLOW);

            /* Install handler to perform access control on datasets */
            bool dataSetAccessHandler(object parameter, ClientConnection connection, DataSetOperation operation, string datasetRef)
            {
                Console.WriteLine("Data set access: "+ datasetRef+" operation: "+ operation.ToString()  + "\n");

                return true;
            }

            iedServer.SetDataSetAccessHandler(dataSetAccessHandler, iedServer);

            /* Install handler to perform read access control on data model elements
            * NOTE: when read access to a data model element is blocked this will also prevent the client
            * to read the data model element  in a data set or enable a RCB instance that uses a dataset
            * containing the restricted data model element.
            */
            MmsDataAccessError readAccessHandler(LogicalDevice ld, LogicalNode ln, DataObject dataObject, FunctionalConstraint fc, ClientConnection connection, object parameter)
            {
                if( dataObject!= null)
                    Console.WriteLine("Read access to "+ld.GetName() + "/"+ln.GetName() + "."+dataObject.GetName() + "\n");
                else
                    Console.WriteLine("Read access to "+ld.GetName() + "/"+ln.GetName() + "\n");

                if (dataObject == null)
                {
                    if (ln.GetName() == "GGIO1")
                    {
                        return MmsDataAccessError.OBJECT_ACCESS_DENIED;
                    }
                }
                else
                {
                    if (ln.GetName() == "GGIO1" && dataObject.GetName() ==  "AnIn1")
                    {
                        return MmsDataAccessError.OBJECT_ACCESS_DENIED;
                    }
                }

                return MmsDataAccessError.SUCCESS;
            }

            iedServer.SetReadAccessHandler(readAccessHandler, null);

            bool directoryAccessHandler(object parameter, ClientConnection connection, IedServer_DirectoryCategory category, LogicalDevice ld)
            {
                switch (category)
                {
                    case IedServer_DirectoryCategory.DIRECTORY_CAT_LD_LIST:

                        Console.WriteLine("Get list of logical devices from "+ connection.GetPeerAddress()+"\n");
                        break;
                    case IedServer_DirectoryCategory.DIRECTORY_CAT_DATASET_LIST:
                        Console.WriteLine("Get list of datasets for LD "+ ld.GetName() + " from "+ connection.GetPeerAddress()+"\n");
                        break;
                    case IedServer_DirectoryCategory.DIRECTORY_CAT_DATA_LIST:
                        Console.WriteLine("Get list of data for LD " + ld.GetName() + " from " + connection.GetPeerAddress() + "\n");
                        break;
                    case IedServer_DirectoryCategory.DIRECTORY_CAT_LOG_LIST:
                        Console.WriteLine("Get list of logs for LD" + ld.GetName() + " from " + connection.GetPeerAddress() + "\n");
                        return false;
                }

                return true;
            }

            iedServer.SetDirectoryAccessHandler(directoryAccessHandler, null);

            /*SettingGroups*/

            LogicalDevice logicalDevice = (LogicalDevice)iedModel.GetModelNodeByShortObjectReference("GenericIO"); ;
            SettingGroupControlBlock settingGroupControlBlock = logicalDevice.GetSettingGroupControlBlock();

            List<PTOC1Settings> ptoc1Settings = new List<PTOC1Settings>();
            ptoc1Settings.Add(new PTOC1Settings { strVal = 1.0f, opDlTmms = 500, rsDlTmms = 500, rstTms = 500 });
            ptoc1Settings.Add(new PTOC1Settings { strVal = 2.0f, opDlTmms = 1500, rsDlTmms = 2500, rstTms = 750 });
            ptoc1Settings.Add(new PTOC1Settings { strVal = 3.0f, opDlTmms = 500, rsDlTmms = 1500, rstTms = 750 });
            ptoc1Settings.Add(new PTOC1Settings { strVal = 3.5f, opDlTmms = 1250, rsDlTmms = 1750, rstTms = 500 });
            ptoc1Settings.Add(new PTOC1Settings { strVal = 3.75f, opDlTmms = 1250, rsDlTmms = 1750, rstTms = 750 });

            void LoadActiveSgValues(int actSG)
            {
                
                DataAttribute dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.StrVal.setMag.f");
                iedServer.UpdateFloatAttributeValue(dataAttribute, ptoc1Settings[actSG - 1].strVal);
                dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.OpDlTmms.setVal");
                iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].opDlTmms);
                dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RsDlTmms.setVal");
                iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].rsDlTmms);
                dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RstTms.setVal");
                iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].rstTms);
            }

            void LoadEditSgValues(int actSG)
            {
                DataObject strVal = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.StrVal");
                DataAttribute setMagF = strVal.GetChildWithFc("setMag.f", FunctionalConstraint.SE);
                iedServer.UpdateFloatAttributeValue(setMagF, ptoc1Settings[actSG - 1].strVal);

                DataObject opDlTmms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.OpDlTmms");
                DataAttribute setVal = opDlTmms.GetChildWithFc("setVal", FunctionalConstraint.SE);
                iedServer.UpdateInt32AttributeValue(setVal, ptoc1Settings[actSG - 1].opDlTmms);

                DataObject rsDlTmms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RsDlTmms");
                DataAttribute rsDlTmms_setVal = rsDlTmms.GetChildWithFc("setVal", FunctionalConstraint.SE);
                iedServer.UpdateInt32AttributeValue(rsDlTmms_setVal, ptoc1Settings[actSG - 1].rsDlTmms);

                DataObject rstTms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RstTms");
                DataAttribute rstTms_setVal = rstTms.GetChildWithFc("setVal", FunctionalConstraint.SE);
                iedServer.UpdateInt32AttributeValue(rstTms_setVal, ptoc1Settings[actSG - 1].rstTms);
            }

            bool activeSGChangedHandler(object parameter, SettingGroupControlBlock sgcb, uint newActSg, ClientConnection connection)
            {
                Console.WriteLine("Switch to setting group "+ newActSg +"\n");

                LoadActiveSgValues(Convert.ToInt32(newActSg));

                return true;
            }

            bool editSGChangedHandler(object parameter, SettingGroupControlBlock sgcb, uint newEditSg, ClientConnection connection)
            {
                Console.WriteLine("Set edit setting group to " + newEditSg + "\n");

                LoadEditSgValues(Convert.ToInt32(newEditSg));

                return true;
            }

            void editSGConfirmationHandler(object parameter, SettingGroupControlBlock sgcb, uint editSg)
            {
                Console.WriteLine("Received edit sg confirm for sg " + editSg + "\n");

                int edit = Convert.ToInt32(editSg);

                DataObject strVal = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.StrVal");
                DataAttribute setMagF = strVal.GetChildWithFc("setMag.f", FunctionalConstraint.SE);
                MmsValue setMagFValue = iedServer.GetAttributeValue(setMagF);

                DataObject opDlTmms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.OpDlTmms");
                DataAttribute setVal = opDlTmms.GetChildWithFc("setVal", FunctionalConstraint.SE);
                MmsValue setValValue = iedServer.GetAttributeValue(setVal);

                DataObject rsDlTmms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RsDlTmms");
                DataAttribute rsDlTmmsSetVal = rsDlTmms.GetChildWithFc("setVal", FunctionalConstraint.SE);
                MmsValue rsDlTmmsSetValValue = iedServer.GetAttributeValue(rsDlTmmsSetVal);

                DataObject rstTms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RstTms");
                DataAttribute rstTmsSetVal = rstTms.GetChildWithFc("setVal", FunctionalConstraint.SE);
                MmsValue rstTmsSetValVaue = iedServer.GetAttributeValue(rstTmsSetVal);

                ptoc1Settings[edit - 1].strVal = setMagFValue.ToFloat();
                ptoc1Settings[edit - 1].opDlTmms = setValValue.ToInt32();
                ptoc1Settings[edit - 1].rsDlTmms = rsDlTmmsSetValValue.ToInt32();
                ptoc1Settings[edit - 1].rstTms = rsDlTmmsSetValValue.ToInt32();

                if (iedServer.GetActiveSettingGroupChangedHandler(sgcb) == edit)
                {
                    LoadActiveSgValues(edit);
                }
            }

            iedServer.SetActiveSettingGroupChangedHandler(activeSGChangedHandler, settingGroupControlBlock, null);
            iedServer.SetEditSettingGroupChangedHandler(editSGChangedHandler, settingGroupControlBlock, null);
            iedServer.SetEditSettingGroupConfirmationHandler(editSGConfirmationHandler, settingGroupControlBlock, null);

            LogicalNode logicalNode = (LogicalNode)iedModel.GetModelNodeByShortObjectReference("GenericIO/LLN0");
            SVControlBlock sampledValuesControlBlock_1 = iedModel.GetSVControlBlock(logicalNode, "SMV1");
            SVControlBlock sampledValuesControlBlock_2 = iedModel.GetSVControlBlock(logicalNode, "SMV2");

            void sVCBEventHandler(SVControlBlock sampledValuesControlBlock, SMVEvent sMVEvent, object parameter)
            {
                Console.WriteLine("control called " + sampledValuesControlBlock.Name + " Event: " + sMVEvent.ToString() + "\n");
            }

            iedServer.SetSVCBHandler(sVCBEventHandler, sampledValuesControlBlock_1, null);
            iedServer.SetSVCBHandler(sVCBEventHandler, sampledValuesControlBlock_2, null);

            iedServer.Start(102);

            if (iedServer.IsRunning())
            {
                Console.WriteLine("Server started");

                GC.Collect();

                DataObject ggio1AnIn1 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.AnIn1");

                DataAttribute ggio1AnIn1magF = (DataAttribute)ggio1AnIn1.GetChild("mag.f");
                DataAttribute ggio1AnIn1T = (DataAttribute)ggio1AnIn1.GetChild("t");

                float floatVal = 1.0f;

                while (running)
                {
                    floatVal += 1f;
                    iedServer.UpdateTimestampAttributeValue(ggio1AnIn1T, new Timestamp(DateTime.Now));
                    iedServer.UpdateFloatAttributeValue(ggio1AnIn1magF, floatVal);
                    Thread.Sleep(100);
                }

                iedServer.Stop();
                Console.WriteLine("Server stopped");
            }
            else
            {
                Console.WriteLine("Failed to start server");
            }

            iedServer.Destroy();
        }
    }
}