﻿using Cli.Interfaces;
using Cli.Models;
using System;
using System.Collections.Generic;
using System.Text;

namespace Cli.Services
{
    public class OutputParser : IOutputParser
    {
        private const float TITLE_FRACTION = 0.25f;
        private const float DESCRIPTION_FRACTION = 0.5f;
        private const float DATE_FRACTION = 0.25f;
        private const int MAX_WIDTH = 200;
        private const int MIN_WIDTH = 30;
        private const int NOF_COLUMNS = 3;
        private const int LEFT_PADDING = 10;

        private readonly Tuple<string, float>[] COLUMNS_DESC =
            {
                new Tuple<string, float>("Title", TITLE_FRACTION),
                new Tuple<string, float>("Description", DESCRIPTION_FRACTION),
                new Tuple<string, float>("Date", DATE_FRACTION)
            };

        private readonly IConsole console;

        public OutputParser(IConsole console) 
        {
            this.console = console;
        }

        public List<string> Parse(List<Entry> list)
        {

            var columns = InitializeColumns();

            var lines = new List<string>
            {
                GenerateSeperationLine(columns),
                GenerateRow(columns),
                GenerateSeperationLine(columns)
            };

            lines.AddRange(GenerateEntryRows(columns, list));

            return lines;
        }

        private string GenerateSeperationLine(List<Column> columns)
        {
            var line = "+";

            foreach (var column in columns)
            {
                var padding = "".PadRight(column.Width, '-');
                line += padding + '+';
            }
            return line;
        }

        private string GenerateRow(List<Column> columns)
        {
            var line = "|";

            foreach (var column in columns)
            {
                line += GenerateColumn(column.Title, column.Width);
            }
            return line;

        }

        private List<string> GenerateEntryRows(List<Column> columns, List<Entry> entries)
        {
            var rows = new List<string>();
            foreach (var entry in entries)
            {
                var todoColumns = new List<Column>()
                {
                    new Column(entry.Title, columns[0].Width),
                    new Column(entry.Description, columns[1].Width),
                    new Column(entry.Date.ToShortDateString(), columns[2].Width)
                };
                rows.Add(GenerateRow(todoColumns));
                rows.Add(GenerateSeperationLine(columns));
            }
            return rows;
        }

        private string GenerateColumn(string columnValue, int columnWidth)
        {
            var whitespaceWidth = columnWidth - columnValue.Length;
            const string ellipsis = "...";
            if (whitespaceWidth < 0)
            {
                var clipLength = Math.Abs(whitespaceWidth) + ellipsis.Length;
                return columnValue.Substring(0, columnValue.Length - clipLength) + ellipsis + '|';
            }
            else
            {
                var padding = "".PadRight(whitespaceWidth, ' ');
                return columnValue + padding + '|';
            }
            
        }

        private List<Column> InitializeColumns()
        {
            var lineWidth = GetLineWidth();
            var columns = new List<Column>();
            float totalFraction = 0;

            foreach (var desc in COLUMNS_DESC)
            {
                columns.Add(new Column()
                {
                    Title = desc.Item1,
                    Width = (int)Math.Floor(lineWidth * desc.Item2)
                });

                totalFraction += desc.Item2;

                if (totalFraction > 1.0)
                {
                    throw new Exception("Fraction width must total 1.");
                }
            }

            return columns;
        }

        private int GetLineWidth()
        {
            var lineWidth = Math.Max(MIN_WIDTH, Math.Min(MAX_WIDTH, console.WindowWidth()));
            lineWidth = lineWidth - LEFT_PADDING;
            return lineWidth;
        }        

        private struct Column
        {

            public Column(string title, int width)
            {
                Title = title;
                Width = width;
            }

            public string Title { get; set; }
            public int Width { get; set; }
        }

        public void Dispose()
        {

        }
    }
}
