using System;
using System.Collections.Generic;
using System.Linq;

namespace MainShell.Hardware
{
    public enum IoPointType
    {
        Input = 0,
        Output = 1
    }

    public enum IoCardType
    {
        Td = 0,
        Acs = 1
    }

    public class DeviceIoPointDefinition
    {
        public int Id { get; set; }
        public string PointKey { get; set; }
        public string Module { get; set; }
        public string Name { get; set; }
        public IoPointType Type { get; set; }
        public IoCardType Card { get; set; }
        public int StationNo { get; set; }
        public int Index { get; set; }
        public int SubIndex { get; set; }
        public string LineNo { get; set; }
        public bool Enabled { get; set; } = true;
        public bool DefaultValue { get; set; }
        public bool IsInverse { get; set; }
        public string Description { get; set; }

        public static string BuildPointKey(IoPointType type, IoCardType card, int stationNo, string lineNo)
        {
            return BuildPointKey(type, card, stationNo, 0, 0, lineNo);
        }

        public static string BuildPointKey(IoPointType type, IoCardType card, int stationNo, int index, int subIndex, string lineNo)
        {
            return string.Format(
                "{0}:{1}:{2}:{3}:{4}:{5}",
                type,
                card,
                stationNo,
                index,
                subIndex,
                (lineNo ?? string.Empty).Trim().ToUpperInvariant());
        }
    }

    public class DeviceIoOutputWriteRequest
    {
        public string PointReference { get; set; }
        public bool OutputOn { get; set; }
    }

    public abstract class DeviceIoPointState
    {
        public int Id { get; set; }
        public string PointKey { get; set; }
        public string Module { get; set; }
        public string Name { get; set; }
        public IoCardType Card { get; set; }
        public int StationNo { get; set; }
        public string LineNo { get; set; }
        public bool Value { get; set; }
        public bool IsInverse { get; set; }
        public abstract IoPointType PointType { get; }
        public abstract DeviceIoPointState Clone();
    }

    public class DeviceInputPointState : DeviceIoPointState
    {
        public override IoPointType PointType => IoPointType.Input;

        public override DeviceIoPointState Clone()
        {
            return new DeviceInputPointState
            {
                Id = Id,
                PointKey = PointKey,
                Module = Module,
                Name = Name,
                Card = Card,
                StationNo = StationNo,
                LineNo = LineNo,
                Value = Value,
                IsInverse = IsInverse
            };
        }
    }

    public class DeviceOutputPointState : DeviceIoPointState
    {
        private Func<bool, bool> _writeAction;

        public bool CommandValue { get; set; }
        public bool CanWrite => _writeAction != null;

        public override IoPointType PointType => IoPointType.Output;

        public void BindWriteAction(Func<bool, bool> writeAction)
        {
            _writeAction = writeAction;
        }

        public bool TrySetOutput(bool outputOn)
        {
            if (_writeAction == null)
            {
                return false;
            }

            var result = _writeAction(outputOn);
            if (result)
            {
                CommandValue = outputOn;
            }

            return result;
        }

        public bool TryTurnOn()
        {
            return TrySetOutput(true);
        }

        public bool TryTurnOff()
        {
            return TrySetOutput(false);
        }

        public override DeviceIoPointState Clone()
        {
            var clone = new DeviceOutputPointState
            {
                Id = Id,
                PointKey = PointKey,
                Module = Module,
                Name = Name,
                Card = Card,
                StationNo = StationNo,
                LineNo = LineNo,
                Value = Value,
                CommandValue = CommandValue,
                IsInverse = IsInverse
            };

            clone.BindWriteAction(_writeAction);
            return clone;
        }
    }

    public class IoModuleSnapshot
    {
        public string Module { get; set; }
        public IReadOnlyList<DeviceIoPointState> Points { get; set; } = new List<DeviceIoPointState>();
    }

    public class DeviceIoSnapshot
    {
        public bool IsValid { get; set; }
        public DateTime Timestamp { get; set; } = DateTime.Now;
        public IReadOnlyDictionary<string, DeviceIoPointState> AllPoints { get; set; } = new Dictionary<string, DeviceIoPointState>();

        public IReadOnlyList<IoModuleSnapshot> Modules
        {
            get
            {
                return AllPoints.Values
                    .GroupBy(x => string.IsNullOrWhiteSpace(x.Module) ? "Default" : x.Module)
                    .Select(g => new IoModuleSnapshot
                    {
                        Module = g.Key,
                        Points = g.Select(x => x.Clone()).ToList()
                    })
                    .ToList();
            }
        }
    }
}
