//	Spludlow Software
//	Copyright © Samuel P. Ludlow 2020 All Rights Reserved
//	Distributed under the terms of the GNU General Public License version 3
//	Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
//	https://www.spludlow.co.uk/LICENCE.TXT
//	The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission
//	v1.14

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

using System.IO;

namespace Spludlow.MetSat
{
	//0: Indicator Section
	//1: Identification Section
	//2: Local Use Section(optional)
	//3: Grid Definition Section
	//4: Product Definition Section
	//5: Data Representation Section
	//6: Bit-Map Section
	//7: Data Section
	//8: End Section

	public class IndicatorSection
	{
		public String Grib;         //	1-4		“GRIB” (coded according to the International Alphabet No. 5)
		public UInt16 Reserved;      //	5-6		Reserved
		public Byte Discipline;     //	7		Discipline – GRIB Master Table Number(see Code Table 0.0)
		public Byte EditionNumber;  //	8		GRIB Edition Number(currently 2)
		public UInt64 Totallength;   //	9-16	Total length of GRIB message in octets(including Section 0)
	}

	public class IdentificationSection
	{
		public UInt32 SectionLength;			//1-4   Length of section in octets(21 or nn)
		public Byte SectionNumber;				//5   Number of Section(“1”)
		public UInt16 GeneratingCentre;			//6-7  Identification of originating/generating centre(see Common Code Table C-1)
		public UInt16 GeneratingSubCentre;		//8-9   Identification of originating/generating sub-centre(allocated by originating/ generating centre)
		public Byte MasterTablesVersion;		//10   GRIB Master Tables Version Number(see Code Table 1.0)
		public Byte LocalTablesVersion;			//11   GRIB Local Tables Version Number(see Code Table 1.1)
		public Byte SignificanceOfReference;    //12   Significance of Reference Time(see Code Table 1.2)
		public UInt16 Year;						//13-14  Year(4 digits)
		public Byte Month;                      //15   Month
		public Byte Day;						//16   Day
		public Byte Hour;						//17   Hour
		public Byte Minute;						//18   Minute
		public Byte Second;						//19   Second
		public Byte ProductionStatus;			//20   Production status of processed data in this GRIB message (see Code Table 1.3)
		public Byte DataType;					//21   Type of processed data in this GRIB message (see Code Table 1.4)
												//22-nn Reserved: need not be present
	}



//1-4   Length of section in octets(nn)
//5   Number of Section(“3”)
//6   Source of grid definition(see Code Table 3.0 and Note 1)
//7-10  Number of data points
//11   Number of octets for optional list of numbers defining number of points(see Note 2)
//12   Interpretation of list of numbers defining number of points(see Code Table 3.11)
//13-14  Grid Definition Template Number(N) (see Code Table 3.1)
//15-xx Grid Definition Template(see Template 3.N, where N is the Grid Definition Template Number given in octets 13-14)
//[xx+1]-nn Optional list of numbers defining number of points(see Notes 2, 3, and 4)

	/// <summary>
	/// GRIB Handling
	/// </summary>
	public class GRIB
	{
		public IndicatorSection IndicatorSection;
		public IdentificationSection IdentificationSection;

		public UInt32 NumberOfDataPoints;

		public UInt16 NumberOfCoordinateValues;
		public UInt16 ProductDefinitionTemplateNumber;

		public UInt16 DataRepresentationTemplateNumber;

		public byte[] Data;

		private int[] IndicatorSection_StringLengths = new int[] { 4 };
		
		private bool Endian = true;

		public GRIB(byte[] data)
		{
			using (MemoryStream stream = new MemoryStream(data))
			{
				this.Load(stream);
			}
		}

		public GRIB(string filename)
		{
			using (FileStream stream = new FileStream(filename, FileMode.Open))
			{
				this.Load(stream);
			}
		}

		public GRIB(FileStream stream)
		{
			this.Load(stream);
		}

		public void Load(Stream stream)
		{
			long read;
			byte[] data;
			
			using (BinaryReader reader = new BinaryReader(stream))
			{
				//0: Indicator Section
				this.IndicatorSection = new IndicatorSection();

				read = Spludlow.Io.BinaryIO.ReadBinary(reader, this.IndicatorSection, this.Endian, this.IndicatorSection_StringLengths);

				if (this.IndicatorSection.Grib != "GRIB")
					throw new ApplicationException("Not a GRIB");

				if ((long)this.IndicatorSection.Totallength != stream.Length)
					throw new ApplicationException("GRIB Bad File length, header:" + this.IndicatorSection.Totallength + ", stream:" + stream.Length);

				//1: Identification Section
				this.IdentificationSection = new IdentificationSection();

				read = Spludlow.Io.BinaryIO.ReadBinary(reader, this.IdentificationSection, this.Endian);	//, this.IdentificationSection_StringLengths);

				if (this.IdentificationSection.SectionNumber != 1)
					throw new ApplicationException("Not GRIB Identification Section");

				reader.ReadBytes((int)this.IdentificationSection.SectionLength - 21);

				//2: Local Use Section(optional)
				UInt32 nextLength = Spludlow.Io.BinaryIO.ReadUInt32(reader, this.Endian);
				Byte nextSectionNumber = reader.ReadByte();

				if (nextSectionNumber == 2)
				{
					reader.ReadBytes((int)nextLength - 5);

					nextLength = Spludlow.Io.BinaryIO.ReadUInt32(reader, this.Endian);
					nextSectionNumber = reader.ReadByte();
				}

				//3: Grid Definition Section
				UInt32 gridDefinitionLength = nextLength;
				Byte gridDefinitionNumber = nextSectionNumber;

				if (gridDefinitionNumber != 3)
					throw new ApplicationException("Not GRIB Grid Definition Section");

				reader.ReadBytes((int)gridDefinitionLength - 5);

				//4: Product Definition Section
				data = this.ReadSection(reader, 4, "Product Definition");

				//		6 - 7   Number of coordinate values after Template(see Note 1)
				this.NumberOfCoordinateValues = Spludlow.Io.BinaryIO.ReadUInt16(data, 5, this.Endian);
				//		8 - 9   Product Definition Template Number(see Code Table 4.0)
				this.ProductDefinitionTemplateNumber = Spludlow.Io.BinaryIO.ReadUInt16(data, 7, this.Endian);
				//		10 - xx  Product Definition Template(see Template 4.X, where X is the Product Definition Template Number given in octets 8 - 9)
				//		[xx + 1] - nn  Optional list of coordinate values(see Notes 2 and 3)

				//throw new ApplicationException(this.NumberOfCoordinateValues + " " + this.ProductDefinitionTemplateNumber); 0 30	 30  Satellite product 

				//5: Data Representation Section
				data = this.ReadSection(reader, 5, "Data Representation");

				//		6 - 9   Number of data points where one or more values are specified in Section 7     when a bit map is present, total number of data points when a bit map is absent
				this.NumberOfDataPoints = Spludlow.Io.BinaryIO.ReadUInt32(data, 5, this.Endian);
				//		10 - 11  Data Representation Template Number(see code Table 5.0)
				this.DataRepresentationTemplateNumber = Spludlow.Io.BinaryIO.ReadUInt16(data, 9, this.Endian);
				//		12 - nn  Data Representation Template(see Template 5.X, where X is the Data     Representation Template Number given in octets 10 - 11)

				//throw new ApplicationException(this.NumberOfDataPoints.ToString());

				//6: Bit-Map Section
				this.ReadSection(reader, 6, "Bit-Map");


				//7: Data Section
				UInt32 dataLength = Spludlow.Io.BinaryIO.ReadUInt32(reader, this.Endian);
				Byte dataNumber = reader.ReadByte();

				if (dataNumber != 7)
					throw new ApplicationException("Not GRIB Data Section");

				this.Data = reader.ReadBytes((int)dataLength - 5);

				//8: End Section
				if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "7777")
					throw new ApplicationException("GRIB Bad End");

				if ((long)this.IndicatorSection.Totallength != stream.Position)
					throw new ApplicationException("GRIB Unexpected data on End");
			}
		}

		private byte[] ReadSection(BinaryReader reader, byte sectionNumber, string sectionName)
		{
			UInt32 length = Spludlow.Io.BinaryIO.ReadUInt32(reader, this.Endian);
			Byte number = reader.ReadByte();

			if (number != sectionNumber)
				throw new ApplicationException("Not GRIB " + sectionName + " Section");

			byte[] data = new byte[length];

			reader.Read(data, 5, (int)length - 5);

			return data;
		}

	}
}