using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;

namespace ModuleDataLib
{
    /// <summary>
    /// Jednotka teploty pro zobrazení
    /// </summary>
    public enum DispUnit { Celsius = 0, Farenheit = 1 }

    /// <summary>
    /// Způsob zabezpečení WLAN
    /// </summary>
    public enum AuthMode { None = 0, Wep = 1, Wpa2 = 2 }

    /// <summary>
    /// Třída pro uložený záznam o měření teploty
    /// </summary>
    public class TempRec
    {
        /// <summary>
        /// Hodnota zastupující nedefinovanou teplotu
        /// </summary>
        public const UInt16 UNDEF_TEMP = 0xFFFF;
        /// <summary>
        /// Hodnota teploty v desetinách stupně Celsia
        /// </summary>
        public Int16 DeciCelsius { get; set; }
        /// <summary>
        /// Stáří teploty v minutách
        /// </summary>
        public UInt16 Age { get; set; }

        /// <summary>
        /// Vrací hodnotu ve stupních Celsia zaokrouhlenou na celé číslo
        /// </summary>
        /// <returns></returns>
        public Int16 GetCelsiusInt()
        {
            return (Int16)Math.Round(DeciCelsius / 10F, 0, MidpointRounding.AwayFromZero);
        }

        /// <summary>
        /// Vrací hodnotu teploty ve zvolných jednotkách jako text (desetiné číslo)
        /// </summary>
        /// <param name="unit"></param>
        /// <returns></returns>
        public string GetStringValue(DispUnit unit)
        {
            if (Age == UNDEF_TEMP)
                return "?";

            if (unit == DispUnit.Celsius)
                return String.Format(
                    "{0}.{1} °C",
                    DeciCelsius / 10,
                    Math.Abs(DeciCelsius) % 10);
            else
            {
                Int16 deciF = todeciF(DeciCelsius);
                return String.Format(
                    "{0}.{1} °F",
                    deciF / 10,
                    Math.Abs(deciF) % 10);
            }
        }

        /// <summary>
        /// Vrací čas naměřené teploty
        /// </summary>
        /// <returns></returns>
        public DateTime GetDateTime()
        {
            return DateTime.Now.AddMinutes(-Age);
        }

        /// <summary>
        /// Převod teploty z deciC na deciF
        /// </summary>
        /// <param name="deciC"></param>
        /// <returns></returns>
        private Int16 todeciF(Int16 deciC)
        {
            // vzorce vychází z převodního vztahu tF = 9*tC/5+32,
            // jsou upraveny pro deci hodnoty a zahrnují zaokrouhlení výsledku
            if (deciC < 0)
                return (Int16)((18 * deciC + 3195) / 10);
            else
                return (Int16)((18 * deciC + 3205) / 10);
        }

    }

    /// <summary>
    /// Bázová třída jednotlivé datové komunikace
    /// </summary>
    public class DataBase
    {
        /// <summary>
        /// Odpověď modulu po úspěchu
        /// </summary>
        protected const string OK = "OK";
        /// <summary>
        /// Prefix příkazu (následují argumenty, otazník popř. vykřičník)
        /// </summary>
        protected string cmdPfx;
        /// <summary>
        /// Úschova odpovědi
        /// </summary>
        protected string reply;
        /// <summary>
        /// Kompletně sestavený příkaz
        /// </summary>
        protected string cmd;

        /// <summary>
        /// Odkaz na instanci modulu
        /// </summary>
        public ModuleBase Module { get; set; }

        /// <summary>
        /// Signalizuje, že data byla získána
        /// </summary>
        public bool Retrieved { get; private set; }

        /// <summary>
        /// Konstruktor
        /// </summary>
        public DataBase(ModuleBase module)
        {
            Module = module;
        }

        /// <summary>
        /// Získá data z modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public virtual bool Get()
        {
            reply = Module.CommObject.SendReceive(String.Format("{0}?", cmdPfx));

            if (String.IsNullOrEmpty(reply))
                return false;
            Retrieved = reply.StartsWith(cmdPfx);
            return Retrieved;
        }

        /// <summary>
        /// Uloží data do modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public virtual bool Upload()
        {
            if (reply == cmd)
                // shoda odpovědi s příkazen znamená úspěch
                return true;

            try
            {
                // návrat OK - znamená také úspěch
                return (Module.CommObject.SendReceive(cmd).StartsWith(OK));
            }
            catch
            {
            }
            // ostatní případy jsou neúspěch
            return false;
        }
    }

    /// <summary>
    /// Třída datové komunikace pro identifikaci modulu
    /// </summary>
    public class DataId : DataBase
    {
        /// <summary>
        /// Pevná část identifikátoru (FLASH) - rozlišuje typ a verzi modulu
        /// </summary>
        public string HwString { get; private set; }
        
        /// <summary>
        /// Volitelná část identifikátoru (EEPROM) - uživatelské jméno modulu
        /// </summary>
        public string IdString { get; set; }
        
        /// <summary>
        /// Vytvoří instanci a nastaví prefix příkazu
        /// </summary>
        public DataId(ModuleBase module) : base(module)
        {
            cmdPfx = "ID";
        }

        /// <summary>
        /// Získá data z modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Get()
        {
            if (!base.Get())
                return false;

            int p = reply.IndexOf(' ', 3);
            
            if (p < 4)
                return false;

            this.HwString = reply.Substring(3, p - 3);
            this.IdString = reply.Substring(p + 1);

            return true;
        }

        /// <summary>
        /// Uloží data do modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Upload()
        {
            cmd = String.Format("{0} {1}", cmdPfx, this.IdString);
            return base.Upload();
        }
    }

    /// <summary>
    /// Třída datové komunikace pro síťová nastavení
    /// </summary>
    public class DataNet : DataBase
    {
        /// <summary>
        /// Statická IP adresa modulu v lokální síti
        /// </summary>
        public IPAddress IP { get; set; }
        /// <summary>
        /// IP adresa brány lokální sítě
        /// </summary>
        public IPAddress Gate { get; set; }
        /// <summary>
        /// Maska lokální sítě
        /// </summary>
        public IPAddress Mask { get; set; }

        /// <summary>
        /// Vytvoří instanci a nastaví prefix příkazu
        /// </summary>
        public DataNet(ModuleBase module) : base(module)
        {
            cmdPfx = "NET";
        }

        /// <summary>
        /// Získá data z modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Get()
        {
            if (!base.Get())
                return false;

            string[] r = reply.Split(' ');
            if (r.Length != 4)
                return false;

            this.IP = IPAddress.Parse(r[1]);
            this.Mask = IPAddress.Parse(r[2]);
            this.Gate = IPAddress.Parse(r[3]);

            return true;
        }

        /// <summary>
        /// Uloží data do modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Upload()
        {
            cmd = String.Format("{0} {1} {2} {3}", cmdPfx, this.IP, this.Mask, this.Gate);
            return base.Upload();
        }
    }

    /// <summary>
    /// Třída datové komunikace pro nastavení WLAN
    /// </summary>
    public class DataWlan : DataBase
    {
        /// <summary>
        /// Síla vysílacího výkonu v rozsahu 0..7
        /// </summary>
        public byte TxPower { get; set; }
        /// <summary>
        /// Metoda zabezpečení WLAN
        /// </summary>
        public AuthMode Auth { get; set; }
        /// <summary>
        /// Identifikátor WLAN (max. 32 znaků bez mezer)
        /// </summary>
        public string Ssid { get; set; }
        /// <summary>
        /// PSK pro WPA2 (oproti specifikaci je podporováno max. 32 znaků bez mezer)
        /// </summary>
        public string Keyword { private get; set; }

        /// <summary>
        /// Vytvoří instanci a nastaví prefix příkazu
        /// </summary>
        public DataWlan(ModuleBase module) : base(module)
        {
            cmdPfx = "WLAN";
        }

        /// <summary>
        /// Získá data z modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Get()
        {
            if (!base.Get())
                return false;

            string[] r = reply.Split(' ');
            if (r.Length < 4)
                return false;

            this.TxPower = Convert.ToByte(r[1]);
            this.Auth = (AuthMode)int.Parse(r[3]);
            this.Ssid = r[2];

            return true;
        }

        /// <summary>
        /// Uloží data do modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Upload()
        {
            cmd = String.Format("{0} {1} {2} {3}", cmdPfx, this.TxPower, this.Ssid, this.Keyword);
            return base.Upload();
        }
    }

    /// <summary>
    /// Třída datové komunikace pro nastavení měření a broadcast vysílání
    /// </summary>
    public class DataMeas : DataBase
    {
        /// <summary>
        /// IP adresa pro broacast vysílání naměřených hodnot
        /// </summary>
        public IPAddress BroadcastIP { get; set; }
        /// <summary>
        /// Interval měření teploty v sec.
        /// </summary>
        public byte MeasInterval { get; set; }
        /// <summary>
        /// Interval vysílání broacastu v sec.
        /// </summary>
        public byte BroadcastInterval { get; set; }

        /// <summary>
        /// Vytvoří instanci a nastaví prefix příkazu
        /// </summary>
        public DataMeas(ModuleBase module) : base(module)
        {
            cmdPfx = "MEAS";
        }

        /// <summary>
        /// Získá data z modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Get()
        {
            if (!base.Get())
                return false;

            string[] r = reply.Split(' ');
            if (r.Length != 4)
                return false;

            this.MeasInterval = Convert.ToByte(r[1]);
            this.BroadcastIP = IPAddress.Parse(r[2]);
            this.BroadcastInterval = Convert.ToByte(r[3]);

            return true;
        }

        /// <summary>
        /// Uloží data do modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Upload()
        {
            cmd = String.Format("{0} {1} {2} {3}", cmdPfx, this.MeasInterval, this.BroadcastIP, this.BroadcastInterval);
            return base.Upload();
        }
    }
    
    /// <summary>
    /// Třída datové komunikace pro nastavení zdroje dat
    /// </summary>
    public class DataSource : DataBase
    {
        /// <summary>
        /// IP adresa zdroje dat (pouze data odesílaná z této adresy budou platná,
        /// adresa je rovněž používána v případě aktivních dotazů)
        /// </summary>
        public IPAddress SensorIP { get; set; }
        /// <summary>
        /// Interval aktivního dotazování v sec. (0 = aktivní dotazování je vypnuto).
        /// Aktivní dotazování je rovněž potlačeno v případě, že jsou k dispozici
        /// platná data z brodcast vysílání.
        /// </summary>
        public byte RequestInterval { get; set; }

        /// <summary>
        /// Vytvoří instanci a nastaví prefix příkazu
        /// </summary>
        public DataSource(ModuleBase module) : base(module)
        {
            cmdPfx = "SRC";
        }

        /// <summary>
        /// Získá data z modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Get()
        {
            if (!base.Get())
                return false;

            string[] r = reply.Split(' ');
            if (r.Length != 3)
                return false;

            this.SensorIP = IPAddress.Parse(r[1]);
            this.RequestInterval = Convert.ToByte(r[2]);

            return true;
        }

        /// <summary>
        /// Uloží data do modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Upload()
        {
            cmd = String.Format("{0} {1} {2}", cmdPfx, this.SensorIP, this.RequestInterval);
            return base.Upload();
        }
    }

    /// <summary>
    /// Třída datové komunikace pro nastavení zobrazování dat
    /// </summary>
    public class DataDisplay : DataBase
    {
        /// <summary>
        /// Maximální povolené stáří zobrazované teploty v min. V případě starší
        /// teploty nebude její hodnota na displeji cílového zařízení zobrazena.
        /// </summary>
        public UInt16 MaxAge { get; set; }
        
        /// <summary>
        /// Jednotky v jakých je hodnota teploty na displeji cílového zařízení zobrazena.
        /// </summary>
        public DispUnit Mode { get; set; }

        /// <summary>
        /// Nastavení jasu (1..5). (0 = vypntí displeje - využito při ladění, kde by mohl
        /// break díky multiplexnímu řízení způsobit jeho poškození).
        /// </summary>
        public byte Brightness { get; set; }

        /// <summary>
        /// Vytvoří instanci a nastaví prefix příkazu
        /// </summary>
        public DataDisplay(ModuleBase module) : base(module)
        {
            cmdPfx = "DISP";
        }

        /// <summary>
        /// Získá data z modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Get()
        {
            if (!base.Get())
                return false;

            string[] r = reply.Split(' ');
            if (r.Length != 4)
                return false;

            this.Mode = (DispUnit)Convert.ToByte(r[1]);
            this.MaxAge = Convert.ToUInt16(r[2]);
            this.Brightness = Convert.ToByte(r[3]);
            
            return true;
        }

        /// <summary>
        /// Uloží data do modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Upload()
        {
            cmd = String.Format("{0} {1} {2} {3}", cmdPfx, (int)this.Mode, this.MaxAge, this.Brightness);
            return base.Upload();
        }
    }

    /// <summary>
    /// Třída datové komunikace pro aktivní načítání dat z datového zdroje
    /// </summary>
    public class DataData : DataBase
    {
        /// <summary>
        /// Aktuální teplota a její stáří (poslední změřená)
        /// </summary>
        public TempRec Current { get; private set; }

        /// <summary>
        /// Minimální teplota a její stáří
        /// </summary>
        public TempRec Minimum { get; private set; }

        /// <summary>
        /// Maximální teplota a její stáří
        /// </summary>
        public TempRec Maximum { get; private set; }

        /// <summary>
        /// Vytvoří instanci a nastaví prefix příkazu
        /// </summary>
        public DataData(ModuleBase module) : base(module)
        {
            cmdPfx = "DATA";
        }

        /// <summary>
        /// Získá data z modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public override bool Get()
        {
            if (!base.Get())
                return false;

            string[] r = reply.Split(' ');
            if (r.Length != 7)
                return false;

            this.Current = new TempRec()
            {
                DeciCelsius = Convert.ToInt16(r[1]),
                Age = Convert.ToUInt16(r[2])
            };
            this.Minimum = new TempRec()
            {
                DeciCelsius = Convert.ToInt16(r[3]),
                Age = Convert.ToUInt16(r[4])
            };
            this.Maximum = new TempRec()
            {
                DeciCelsius = Convert.ToInt16(r[5]),
                Age = Convert.ToUInt16(r[6])
            };

            return true;
        }

        /// <summary>
        /// Maže záznam o minimální a maximální teplotě z EEPROM modulu
        /// </summary>
        /// <returns>True = úspěch</returns>
        public bool ClearMinMax()
        {
            try
            {
                return (Module.CommObject.SendReceive(String.Format("{0}!", cmdPfx)).StartsWith(OK));
            }
            catch
            {
            }
            return false;
        }

        /// <summary>
        /// Uložení dat není podporováno
        /// </summary>
        /// <returns></returns>
        public override bool Upload()
        {
            throw new NotImplementedException();
        }
    }
}
