using Edge.Core.IndustryStandardInterface.Pump.Fdc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Edge.Core.IndustryStandardInterface.Pump
{
    /// <summary>
    /// Implements this interface indicates the class have the ability to control the physical 
    /// pump and expose the controller interface by following Fdc pattern. 
    /// </summary>
    public interface IFdcPumpController //: IDeviceController
    {
        event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;
        event EventHandler<FdcTransactionDoneEventArg> OnCurrentFuellingStatusChange;

        /// <summary>
        /// will be called once Fdc server starting.
        /// Fdc server will pass in necessary parameters via this function to each FdcPumpController. 
        /// </summary>
        /// <param name="parameters"></param>
        void OnFdcServerInit(Dictionary<string, object> parameters);
        string Name { get; }
        /// <summary>
        /// Gets the Identification of the pump for the system. Is the logical number of the pump.
        /// e.g.: A site may have 4 physical pumps, each physical pump have 2 sides(fuelling point), then it's the
        /// pump id from 1 to 8.
        /// Then may have the model of physical pump with 6 nozzels, 3 for each side, but each side may grouped into 2 fuelling points,
        /// the first fuelling point in one side contains 1 nozzel, while the second contains 2 nozzels, so in this case, 
        /// this physical pump actually have 4 fuelling points, also means this physical pump should have 4 pump ids.
        /// </summary>
        int PumpId { get; }

        /// <summary>
        /// Gets the internal identification of the fuelling position.
        /// Most likely it'll be only used in binary protocol parsing level.
        /// At least for Wayne pump, it can configed an internal address for each fuelling position via physical pump main board,
        /// this Id is critical for the case of multiple Pumps connected into one ComPort.
        /// If a device didn't have to check binary message with this id, then put any value for it.
        /// </summary>
        int PumpPhysicalId { get; }

        IEnumerable<LogicalNozzle> Nozzles { get; }

        /// <summary>
        /// Gets the money/amount digits.
        /// the Money/amount value read from pump is always an int value, need convert with this digits settings to readable format.
        /// </summary>
        int AmountDecimalDigits { get; }

        /// <summary>
        /// Gets the volume digits
        /// </summary>
        int VolumeDecimalDigits { get; }

        /// <summary>
        /// price digits typically not the same with Amount digits
        /// </summary>
        int PriceDecimalDigits { get; }

        int VolumeTotalizerDecimalDigits { get; }


        Task<LogicalDeviceState> QueryStatusAsync();

        /// <summary>
        /// 
        /// </summary>
        /// <returns>MoneyTotalizer:VolumnTotalizer</returns>
        Task<Tuple<long, long>> QueryTotalizerAsync(byte logicalNozzleId);
        //string QueryConfiguration();


        Task<bool> SuspendFuellingAsync();
        Task<bool> ResumeFuellingAsync();


        Task<bool> ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId);


        Task<bool> AuthorizeAsync(byte logicalNozzleId);
        Task<bool> UnAuthorizeAsync(byte logicalNozzleId);
        Task<bool> AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId);
        Task<bool> AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId);

        /// <summary>
        /// 凑整
        /// </summary>
        /// <param name="amount">expecting fueling to this amount, value is without decimal point</param>
        /// <returns></returns>
        Task<bool> FuelingRoundUpByAmountAsync(int amount);

        /// <summary>
        /// 凑整
        /// </summary>
        /// <param name="amount">expecting fueling to this volum, value is without decimal point</param>
        /// <returns></returns>
        Task<bool> FuelingRoundUpByVolumeAsync(int volume);

        /// <summary>
        /// lock a nozzle for not allow it for fueling
        /// </summary>
        /// <param name="logicalNozzleId"></param>
        /// <returns></returns>
        Task<bool> LockNozzleAsync(byte logicalNozzleId);

        /// <summary>
        /// unlock a locked nozzle 
        /// </summary>
        /// <param name="logicalNozzleId"></param>
        /// <returns></returns>
        Task<bool> UnlockNozzleAsync(byte logicalNozzleId);
    }

    /// <summary>
    /// 每把逻辑枪代表着能加出某个油品的虚拟枪。
    /// 大多数情况下逻辑枪数==物理枪数。
    /// 存在一把物理枪可以加出三种油的情况，这种情况下，即为三把逻辑枪，但共用一个Physical Id.
    /// </summary>
    public class LogicalNozzle
    {
        private LogicalNozzle() { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pumpId">the pump(FuelPoint) id of this nozzle is hosted</param>
        /// <param name="physicalId"></param>
        /// <param name="logicalId">must >=1</param>
        /// <param name="realPriceOnPhysicalPump"></param>
        public LogicalNozzle(int pumpId, byte physicalId, byte logicalId, int? realPriceOnPhysicalPump)
        {
            if (logicalId < 1) throw new ArgumentException("Nozzle logical id must be >=1");
            this.PumpId = pumpId;
            this.RealPriceOnPhysicalPump = realPriceOnPhysicalPump;
            this.PhysicalId = physicalId;
            this.LogicalId = logicalId;
        }

        public int PumpId { get; }

        /// <summary>
        /// nozzle PhysicalId should be assgined at hardware level for a physical pump.
        /// so it's device dependent, can be confirmed in Pump comm protocol doc.
        /// like in Ifsf protocol, physical id for a nozzle is from 0x11 - 0x18.
        /// like in Wayne dart protocol, physical id for na nozzle is from 1 - 8.
        /// a physical nozzle may have multiple fuel products assgined(blending).
        /// most likely it will repeat in each individual pump.
        /// </summary>
        public byte PhysicalId { get; }

        /// <summary>
        /// Gets or sets the logical id for a nozzle, value must range from 1 to N.
        /// in LiteFcc, we follow the rule of, that you face to a FuelPoint,
        /// the most left side nozzle is always logicalId 1,
        /// and the nozzle right to it, it's 2, and then 3...
        /// </summary>
        public byte LogicalId { get; }

        /// <summary>
        /// Gets or sets the real price which read from physical pump, without decimal points.
        /// some pumps can get price when first time FC connected to it, but some are not.
        /// So it may null though the physical pump side price is set (have not read by FC).
        /// So it may diff from the FC side value(by a failure in price change request, and that value will be saved into db)
        /// 
        /// </summary>
        public int? RealPriceOnPhysicalPump { get; set; }

        /// <summary>
        /// Gets or sets the expecting price which set from FC side, without decimal points.
        /// FC side could change the pump price (via price change request), this value will send to physical 
        /// pump and finally saved into FC db.
        /// but this does NOT gurantee the consistent since the comm between the FC and pump may fail.
        /// </summary>
        public int? ExpectingPriceOnFcSide { get; set; }

        /// <summary>
        /// Gets or sets the vol totalizer value for this nozzle, without decimal points.
        /// </summary>
        public int? VolumeTotalizer { get; set; }


        public LogicalDeviceState? LogicalState { get; set; }
    }

    public class FdcTransactionDoneEventArg : System.EventArgs
    {
        public FdcTransactionDoneEventArg(FdcTransaction transaction)
        {
            this.Transaction = transaction;
        }

        public FdcTransaction Transaction { get; private set; }
    }

    public class FdcTransaction
    {
        public LogicalNozzle Nozzle { get; set; }

        /// <summary>
        /// without decimal points
        /// </summary>
        public int Amount { get; set; }

        /// <summary>
        /// without decimal points
        /// </summary>
        public int Volumn { get; set; }

        /// <summary>
        /// without decimal points
        /// </summary>
        public int Price { get; set; }

        public int Barcode { get; set; }

        /// <summary>
        /// Gets or set the seq number which typically generated on phsyical pump side for 
        /// unique a trx, for most pumps, the number will be rotate and reused after a period of time,
        /// this is fine since FDC server side check the duplication only by a short time period range,
        /// not the full range scan.
        /// for some pumps, it didn't have this value at all, then need generate one in FC pump handler side, 
        /// an unique value is requried.
        /// </summary>
        public int SequenceNumberGeneratedOnPhysicalPump { get; set; }

        /// <summary>
        /// the money amount totalizer value after this new trx done, without decimal points.
        /// the trx from a pump may not carry on amount totalizer value, it device dependent.
        /// </summary>
        public int? AmountTotalizer { get; set; }

        /// <summary>
        /// the volume amount totalizer value after this new trx done, without decimal points.
        /// the trx from a pump may not carry on volume totalizer value, it device dependent.
        /// </summary>
        public int? VolumeTotalizer { get; set; }

        /// <summary>
        /// Gets or sets if the fueling transaction still on going.
        /// True for transaction is done, False for on going.
        /// </summary>
        public bool Finished { get; set; }

        /// <summary>
        /// Gets or sets the start time of this fuel trx.
        /// leave it null will set it to: (SaleEndTime - x minutues), the default x is 3 and could be changed in future in FdcServerApp.
        /// </summary>
        public DateTime? SaleStartTime { get; set; }

        /// <summary>
        /// Gets or sets the end time of this fuel trx.
        /// leave it null will set it to DateTime.Now in FdcServerApp.
        /// </summary>
        public DateTime? SaleEndTime { get; set; }
    }
}
