using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace KonvertorSorCsv
{
    class SorParser
    {
        private string filepath;
        private BinaryReader br;
        private MemoryStream ms;
        private List<Block> blocks = new List<Block>(); //info o blokoch
        Dictionary<string, Block> dict; //slovnik blokov
        private List<DataPoint> datapts; //datove body
        private string outdiag = ""; //log vystup parsera
        private string parserlog = ""; //chybovy log
        private int eventcount; //pocet udalosti
        private List<int> eventloc; //list indexov eventov

        private string fixedParamstext; //text obsah FixedParams


        public SorParser(string filepath)
        {
            this.filepath = filepath;
            datapts = new List<DataPoint>();
            //nacitanie suboru
            ms = new MemoryStream();
            try
            {
                FileStream fs = new FileStream(filepath, FileMode.Open);
                fs.CopyTo(ms);
                fs.Dispose();
            }
            catch (Exception ex)
            {
                parserlog += ("Exception: " + ex.Message + '\n');
                datapts = null;
            }

            //kontrola ci subor nie je prazdny
            br = new BinaryReader(ms);
            if (br.BaseStream.Length == 0)
            {
                parserlog += ("Empty file!" + '\n');
                datapts = null;
            }
        }
        private void processDataPointsBlock()
        {           
            //citam prvy blok
            br.BaseStream.Position = 0;
            blocks.Add(getBlock());

            //nacitam pocet blokov v subore
            ushort blockcount = br.ReadUInt16();
            outdiag += String.Format("Block count: " + blockcount+'\n');

            //citam vsetky bloky v hlavicke
            for (int i = 1; i < blockcount; ++i)
            {
                blocks.Add(getBlock());
            }
            //spocitam start a end
            uint tempstart = 0;
            foreach (Block bl in blocks)
            {
                bl.start = tempstart;
                tempstart += (bl.size);
                bl.end = bl.start + bl.size - 1;

            }

            //vytvorim slovnik blokov
            dict = new Dictionary<string, Block>();
            foreach (Block bl in blocks)
            {
                dict.Add(bl.name, bl);
            }

            //vypisem bloky
            outdiag += String.Format("                  name       start     end     size start(hex) end(hex)\n");
            
            foreach (Block bl in blocks)
            {
                outdiag += (bl.ToString()+'\n');
            }

            //citam datapoints
            br.BaseStream.Position = dict["DataPts"].start;
            br.BaseStream.Position += ("DataPts".Length + 1);   //preskocim uvodny string ukonceny null   
            uint datapointcount = br.ReadUInt32(); //precitam pocet datovych bodov
            br.BaseStream.Position += 2; //skok o dva byte dopredu
            br.BaseStream.Position += 4; // 4byte stejne jako datapointcount
            uint scalingfactor = br.ReadUInt16(); // precitam scalingfactor
            outdiag += String.Format("datapointcount: {0}      scalingfactor: {1}\n", datapointcount, scalingfactor);

            // vytvorenie y osi
            double[] y = new double[datapointcount];
            for (uint i = 0; i < datapointcount; ++i)
            {
                y[i] = br.ReadUInt16() * scalingfactor * 0.000001;
            }
            double max = y.Max(); //spocitam maximum
            // posuniem y os
            for (uint i = 0; i < datapointcount; ++i)
            {
                y[i] = max - y[i];
            }

            //citam fxdparams 
            double dx; // sample spacing in km
            double samplespacing = 0; //sample spacing in us
            double ior = 0; //index of refraction //fixed parameter block
            double sol = 0.299792458; //0.299792458 km/usec speed of light in vacuum

            br.BaseStream.Position = dict["FxdParams"].start;
            //samplespacing in us
            br.BaseStream.Position = dict["FxdParams"].start + dict["FxdParams"].name.Length + 1 + 20; //skok na 20ty byte v bloku
            samplespacing = br.ReadUInt32() * 0.00000001;
            outdiag += ("Samplespacing: "+samplespacing+"us"+'\n');
            //index of refraction
            br.BaseStream.Position = dict["FxdParams"].start + dict["FxdParams"].name.Length + 1 + 28; //skok na 28 byte po mene
            ior = br.ReadUInt32() * 0.00001;
            outdiag += ("Index of refraction: "+ior+'\n');
            dx = samplespacing * sol / ior;  // sample spacing in km
            outdiag += (String.Format("samplespacing; {0} km", dx) + '\n');



            //vytvorenie xovej osi v km
            double[] x = new double[datapointcount];
            for (uint i = 0; i < datapointcount; ++i)
            {
                x[i] = i * dx; //km
            }
            //vytvorenie a naplnenie struktury datapts
            for (uint i = 0; i < datapointcount; ++i)
            {
                datapts.Add(new DataPoint(x[i], y[i]));
            }
            outdiag += String.Format("DataPoint count: {0}\n", datapts.Count);
        }

        private string getLabelString() //cita string ukonceny 0 bytom
        {
            int a; //aktualny byte
            string s = "";
            int count = 0; //precitany pocet bytov
            do
            {
                a = br.ReadByte();
                ++count;
                if (a == 0)
                {
                    break;
                }
                s += (char)a;
                //osetrenie nekonecnej smycky
                if (count >= br.BaseStream.Length) break;
            }
            while (a != 0);
            return s;
        }

        private Block getBlock() //precita blok
        {
            string labelstring = getLabelString();
            br.BaseStream.Position += 2; //2 byte oznacujuce verziu
            uint blocksize = br.ReadUInt32(); //precitam 4 byte pocet bytov v Map bloku
            return new Block(labelstring, blocksize);
        }

        public string getParseDiag() //vrati diag vystup parsera
        {
            return outdiag;
        }

        public string getParseLog() //vrati parserlog
        {
            return parserlog;
        }

        public void DisposeStreams()
        {
            //odstranim streamy z pameti
            br.Dispose();
            ms.Dispose();
        }

        private void processEventBlocks()
        {
            br.BaseStream.Position = dict["FxdParams"].start + dict["FxdParams"].name.Length + 1 + 20;
            double samplespacing = br.ReadUInt32() * 0.00000001; //v us

            br.BaseStream.Position = dict["KeyEvents"].start;
            br.BaseStream.Position += "KeyEvents".Length + 1;
            eventcount = br.ReadUInt16(); //2B pocet udalosti

            outdiag += "\n\n";
            outdiag += ("FxdParams block\n");
            outdiag += String.Format("Event count: {0}\n",eventcount);
            eventloc = new List<int>();
            int eventstart;
            for (int i = 0; i < eventcount; ++i)
            {
                br.BaseStream.Position += (26);
                eventstart = Convert.ToInt32((br.ReadUInt32() / (samplespacing / 0.0001)));
                outdiag += String.Format("{0}.event point loc: {1}\n", i, eventstart);
                br.BaseStream.Position += (12 + 1);
                eventloc.Add(eventstart);
            }
            // eventy sa ukladaju v rozostupe 0.0001 us
            //meranie ma ine rozostupy bodov
        }

        public void parse()
        {
            processDataPointsBlock();
            processEventBlocks();
            processFixedParamsBlock();
            DisposeStreams();
        }

        public List<DataPoint> getDataPoints()
        {
            //osetrim chybovy stav, ked konstruktor nastavil datapts na null
            if (datapts == null) return null;
            return datapts;
        }

        public List<int> getEventLocIndex()
        {
            if (datapts == null) return null;
            return eventloc;
        }

        private void processFixedParamsBlock()
        {
            br.BaseStream.Position = dict["FxdParams"].start; //skocim na zaciatok bloku
            br.BaseStream.Position += "FxdParams".Length + 1; //skok o dlzku stringu + 1 byte

            //zapis do diag
            fixedParamstext += String.Format("\n\nFixedParamsBlock:");

            //citam datum vo Unix formate
            double unixseconds = (double)br.ReadUInt32();
            System.DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
            dt = dt.AddSeconds(unixseconds).ToLocalTime(); //konverzia do Windows datetime struktury
            fixedParamstext += String.Format("\nDatetime(UTC): ".PadRight(18)+ dt);

            //jednotka zobrazenia dlzky
            string unit = "";
            unit += br.ReadChar();
            unit += br.ReadChar();
            //km: kilometers
            //mt: meters
            //ft: feet
            //kf: kilo - feet
            //mi: miles
            switch (unit)
            {
                case "km":
                    {
                        unit = "kilometers";
                    }
                    break;
                case "mt":
                    {
                        unit = "meters";
                    }
                    break;
                case "ft":
                    {
                        unit = "feet";
                    }
                    break;
                case "kf":
                    {
                        unit = "kilo - feet";
                    }
                    break;
                case "mi":
                    {
                        unit = "miles";
                    }
                    break;
            }
            fixedParamstext += String.Format("\nUnit: ".PadRight(18) + unit);

            //vlnova dlzka
            int wavelength = br.ReadUInt16()/10; //je to ulozene ako 10 nasobok hodnoty v nm
            fixedParamstext += String.Format("\nWavelength: ".PadRight(18) + wavelength);

            br.BaseStream.Position += 10; //skok o 10 bytov

            //pulse width v ns
            double pulsewidth = (double)br.ReadUInt16();
            fixedParamstext += String.Format("\nPulsewidth: ".PadRight(18) + pulsewidth + " ns");

            //sample spacing 4B v ps
            double samplespacing = br.ReadUInt32() * 0.00000001;
            fixedParamstext += String.Format("\nSample spacing: ".PadRight(18) + samplespacing + " us");

            //pocet datovych bodov
            uint datapointscount = br.ReadUInt32();
            fixedParamstext += String.Format("\nDatapoint count: ".PadRight(18) + datapointscount);

            //index of refraction
            double indexofref = br.ReadUInt32()*0.00001;
            fixedParamstext += String.Format("\nRefr. index: ".PadRight(18) + indexofref);

            //backscattering coefficient 2B
            double backscatcoef = br.ReadUInt16() * (-0.1); //v db
            fixedParamstext += String.Format("\nBC: ".PadRight(18) + backscatcoef);

            //number of averages 4B
            double nofav = br.ReadUInt32();
            fixedParamstext += String.Format("\nAverages num: ".PadRight(18) + nofav);

            //averaging time 2B
            double avertime = br.ReadUInt16() * 0.1;
            fixedParamstext += String.Format("\nAveraging time: ".PadRight(18) + avertime + " s");

            //range 4B
            double range = br.ReadUInt32()*2*0.00001;
            fixedParamstext += String.Format("\nRange: ".PadRight(18) + range + " km");

            //skok 14B
            br.BaseStream.Position += 14;

            //loss threshold 2B
            double lossthr = br.ReadUInt16() * 0.001;
            fixedParamstext += String.Format("\nLoss thr: ".PadRight(18) + lossthr + " dB");

            //refl threshold 2B
            double reflthr = br.ReadUInt16() * (-0.001);
            fixedParamstext += String.Format("\nRefl. thr: ".PadRight(18) + reflthr + " dB");

            //EOT threshold 2B
            double eotthr = br.ReadUInt16() * 0.001;
            fixedParamstext += String.Format("\nEOTthr: ".PadRight(18) + eotthr + " dB");

            //trace type 2B
            string tracetype = "";
            tracetype += br.ReadChar();
            tracetype += br.ReadChar();
            switch (tracetype)
            {
                case "ST":
                    {
                        tracetype = "standard trace";
                    }
                    break;
                case "RT":
                    {
                        tracetype = "reverse trace";
                    }
                    break;
                case "DT":
                    {
                        tracetype = "difference trace";
                    }
                    break;
                case "RF":
                    {
                        tracetype = "reference";
                    }
                    break;
            }
            fixedParamstext += String.Format("\nTracetype: ".PadRight(18) + tracetype);
            outdiag += fixedParamstext;
        }

        public string getFixedParamsText()
        {
            return fixedParamstext;
        }


    }
}
