using System;
using System.Collections.Generic;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;
using LeafWeb.Core.Models;

namespace LeafWeb.Core.Services
{
	public class LeafInputCsvParser : IDisposable
	{
		private readonly FileSystemInfo _csvFile;
		private readonly StreamReader _reader;
		private readonly CsvReader _csv;
		
		public LeafInputCsvParser(FileSystemInfo csvFile)
		{
			_csvFile = csvFile;
			if (!csvFile.Exists)
				throw new FileNotFoundException("Cannot find file '" + csvFile.Name + "'");

			_reader = File.OpenText(csvFile.FullName);
			var csvConfiguration = new CsvConfiguration {HasHeaderRecord = false};
			_csv = new CsvReader(_reader, csvConfiguration);
		}

		public LeafInput Parse()
		{
			// First 10 lines
			var leafInput = ParseLeafInput();
			leafInput.FileName = _csvFile.Name;

			// Next 3 (Header, Units, and Values)
			leafInput.Site = ParseLeafInputSite();

			// Next 3 (Header, Units, and Values)
			leafInput.Photosynthetic = ParseLeafInputPhotosynthetic();

			// Remaining lines (Header, Units, and Data)
			leafInput.Data = ParseLeafInputData();
			
			return leafInput;
		}

		private LeafInput ParseLeafInput()
		{
			var items = new List<string>();
			for (var i = 0; i < 10; i++)
			{
				if (!_csv.Read())
					throw new ParseException("Could not read line number " + _csv.Row);

				string field;
				if (!_csv.TryGetField(0, out field))
					throw new ParseException("Could not read first field on line number " + _csv.Row);
				items.Add(field);
			}
			return ParsedObjectFactory<LeafInput>.Create(items.ToArray());
		}

		private string[] GetNextCsvRowValues()
		{
			// get values from row
			if (!_csv.Read())
				return null;

			// put all the values from this row into an array
			var values = new List<string>();
			var index = 0;
			string value;
			while (_csv.TryGetField(index, out value))
			{
				values.Add(value);
				index++;
			}
			return values.ToArray();
		}

		private LeafInputSite ParseLeafInputSite()
		{
			var titles = GetNextCsvRowValues();
			if (titles == null)
				throw new ParseException("Could not read site header row on line number " + _csv.Row);
			var units = GetNextCsvRowValues();
			if (units == null)
				throw new ParseException("Could not read site units row on line number " + _csv.Row);
			var values = GetNextCsvRowValues();
			if (values == null)
				throw new ParseException("Could not read site value row on line number " + _csv.Row);
		
			var items = new List<Tuple<string, string>>();
			for (var i = 0; i < titles.Length && i < values.Length; i++)
			{
				var item = Tuple.Create(titles[i], values[i]);
				items.Add(item);
			}
			return ParsedObjectFactory<LeafInputSite>.Create(items.ToArray());
		}

		private LeafInputPhotosynthetic ParseLeafInputPhotosynthetic()
		{
			var titles = GetNextCsvRowValues();
			if (titles == null)
				throw new ParseException("Could not read photosynthetic header row on line number " + _csv.Row);
			var units = GetNextCsvRowValues();
			if (units == null)
				throw new ParseException("Could not read photosynthetic units row on line number " + _csv.Row);
			var values = GetNextCsvRowValues();
			if (values == null)
				throw new ParseException("Could not read photosynthetic value row on line number " + _csv.Row);
		
			var items = new List<Tuple<string, string>>();
			for (var i = 0; i < titles.Length && i < values.Length; i++)
			{
				var item = Tuple.Create(titles[i], values[i]);
				items.Add(item);
			}
			return ParsedObjectFactory<LeafInputPhotosynthetic>.Create(items.ToArray());
		}

		private LeafInputData[] ParseLeafInputData()
		{
			var titles = GetNextCsvRowValues();
			if (titles == null)
				throw new ParseException("Could not read data header row on line number " + _csv.Row);
			var units = GetNextCsvRowValues();
			if (units == null)
				throw new ParseException("Could not read data units row on line number " + _csv.Row);

			var valueArrays = new List<string[]>();
			while (true)
			{
				var values = GetNextCsvRowValues();
				if (values == null)
					break;
				valueArrays.Add(values);
			}
			return ParsedObjectFactory<LeafInputData>.Create(titles, valueArrays.ToArray());
		}

		public void Dispose()
		{
			_reader.Dispose();
		}

		public static void ExportCsv(string filename, IEnumerable<LeafInput> leafInputs)
		{
			using (var textWriter = new StreamWriter(filename))
			{
				var csv = new CsvWriter(textWriter);
				csv.WriteRecords(leafInputs);
			}
		}
	}
}
