using ClosedXML.Excel;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace AsbCloudInfrastructure.Services.DailyReport
{

    internal class CellAddress: IXLAddress
    {
        const int excelLettersCount = 'Z' - 'A' + 1;
        public int RowNumber { get; set; }
        public int ColumnNumber { get; set; }

        public string ColumnLetter 
        { 
            get { return CalcColumnLetter(); } 
        }

        public bool FixedColumn { get; set; }

        public bool FixedRow { get; set; }

        public string UniqueId => ToString(XLReferenceStyle.A1, true);

        public IXLWorksheet? Worksheet { get; set; }

        public CellAddress(IXLWorksheet? worksheet, int row, int colunm)
        {
            Worksheet = worksheet;
            RowNumber = row;
            ColumnNumber = colunm;
        }
        
        public static CellAddress operator + (CellAddress a, CellAddress b)
            => new CellAddress(a.Worksheet, a.RowNumber + b.RowNumber, a.ColumnNumber + b.ColumnNumber);
        public static CellAddress operator +(CellAddress a, (int row, int column) b)
            => new CellAddress(a.Worksheet, a.RowNumber + b.row, a.ColumnNumber + b.column);
        public static CellAddress operator - (CellAddress a, CellAddress b)
            => new CellAddress(a.Worksheet, a.RowNumber - b.RowNumber, a.ColumnNumber - b.ColumnNumber);
        public static bool operator == (CellAddress a, CellAddress b)
            => a.RowNumber == b.RowNumber && a.ColumnNumber == b.ColumnNumber;
        public static bool operator !=(CellAddress a, CellAddress b)
            => !(a == b);

        private string CalcColumnLetter()
        {
            string letter = "";
            var columnNumber = ColumnNumber;
            while (columnNumber > 0)
            {
                int modulo = (columnNumber - 1) % excelLettersCount;
                letter = Convert.ToChar('A' + modulo) + letter;
                columnNumber = (columnNumber - modulo) / excelLettersCount;
            }

            return letter;
        }

        public CellAddress Copy() 
            => new CellAddress(Worksheet, RowNumber, ColumnNumber)
            {
                FixedColumn = this.FixedColumn,
                FixedRow = this.FixedRow,
            };

        public override string ToString()
            => ToString(XLReferenceStyle.A1);

        public string ToString(XLReferenceStyle referenceStyle)
            => ToString(referenceStyle, false);

        public string ToString(XLReferenceStyle referenceStyle, bool includeSheet)
        {
            if (referenceStyle == XLReferenceStyle.R1C1)
                throw new NotImplementedException("R1C1 - style doesn't implemented");

            var sb = new StringBuilder();
            if (includeSheet && Worksheet is not null)
                sb.Append('$')
                  .Append(Worksheet.Name)
                  .Append('.');

            if (FixedColumn)
                sb.Append('$');

            sb.Append(ColumnLetter);

            if (FixedRow)
                sb.Append('$');

            sb.Append(RowNumber);

            return sb.ToString();
        }

        public string ToStringFixed()
            => ToStringFixed(XLReferenceStyle.A1);

        public string ToStringFixed(XLReferenceStyle referenceStyle)
            => ToStringFixed(referenceStyle, false);

        public string ToStringFixed(XLReferenceStyle referenceStyle, bool includeSheet)
        {
            if (referenceStyle == XLReferenceStyle.R1C1)
                throw new NotImplementedException("R1C1 - style doesn't implemented");

            var sb = new StringBuilder();
            if (includeSheet && Worksheet is not null)
                sb.Append('$')
                  .Append(Worksheet.Name)
                  .Append('.');

            sb.Append('$');
            sb.Append(ColumnLetter);            
            sb.Append('$');
            sb.Append(RowNumber);

            return sb.ToString();
        }

        public string ToStringRelative()
            => ToStringRelative(false);

        public string ToStringRelative(bool includeSheet)
        {
            var sb = new StringBuilder();
            if (includeSheet && Worksheet is not null)
                sb.Append('$')
                  .Append(Worksheet.Name)
                  .Append('.');

            sb.Append(ColumnLetter);
            sb.Append(RowNumber);

            return sb.ToString();
        }

        public bool Equals(IXLAddress? x, IXLAddress? y)
            => x?.ColumnNumber == y?.ColumnNumber &&
            x?.RowNumber == y?.RowNumber &&
            x?.FixedColumn == y?.FixedColumn &&
            x?.FixedRow == y?.FixedRow &&
            x?.Worksheet == y?.Worksheet;

        public override int GetHashCode()
            => base.GetHashCode();

        public int GetHashCode([DisallowNull] IXLAddress obj)
            => obj.GetHashCode();

        public bool Equals(IXLAddress? other)
            => Equals(this, other);

        public override bool Equals(object? obj)
        {
            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            if (obj is null)
            {
                return false;
            }

            if (obj is CellAddress address)
                return this == address;

            return false;
        }
    }

}