using Org.BouncyCastle.Pkcs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace UniMarc
{
    public class MarcSubfield
    {
        public char Code { get; set; }
        public string Value { get; set; }

        public MarcSubfield(char code, string value)
        {
            Code = code;
            Value = value;
        }

        public override string ToString()
        {
            return $"▼{Code}{Value}";
        }
    }

    public class MarcField
    {
        public string Tag { get; set; }
        public string Indicators { get; set; } = "  ";
        public string ControlValue { get; set; }
        public List<MarcSubfield> Subfields { get; set; } = new List<MarcSubfield>();

        public bool IsControlField => int.TryParse(Tag, out int tagNum) && tagNum < 10;

        public MarcField(string tag)
        {
            Tag = tag;
        }

        public string GetSubfieldValue(char code)
        {
            var sub = Subfields.FirstOrDefault(s => s.Code == code);
            return sub != null ? sub.Value : string.Empty;
        }

        public override string ToString()
        {
            if (IsControlField)
                return $"{Tag}\t  \t{ControlValue}▲";

            StringBuilder sb = new StringBuilder();
            sb.Append($"{Tag}\t{Indicators}\t");
            foreach (var sub in Subfields)
            {
                sb.Append(sub.ToString());
            }
            sb.Append("▲");
            return sb.ToString();
        }
    }

    public class MarcParser
    {
        public string Leader { get; set; } = "00000nam  2200000 k 4500";
        public List<MarcField> Fields { get; set; } = new List<MarcField>();

        private const char SUBFIELD_MARKER = '▼';
        private const char FIELD_TERMINATOR = '▲';
        private const char RECORD_TERMINATOR = '\x1D';

        public MarcParser() { }

        public void ParseMnemonic(string data)
        {
            Fields.Clear();
            if (string.IsNullOrEmpty(data)) return;

            string[] lines = data.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var line in lines)
            {
                string cleanLine = line.Trim();
                if (cleanLine.Length < 3) continue;

                string tag = cleanLine.Substring(0, 3);
                MarcField field = new MarcField(tag);

                string[] parts = cleanLine.Split('\t');

                if (field.IsControlField)
                {
                    if (parts.Length >= 3)
                        field.ControlValue = parts[2].TrimEnd(FIELD_TERMINATOR, ' ');
                    else
                        field.ControlValue = cleanLine.Substring(Math.Min(cleanLine.Length, 3)).Trim('\t', ' ', FIELD_TERMINATOR);
                }
                else
                {
                    if (parts.Length >= 2)
                        field.Indicators = parts[1].PadRight(2).Substring(0, 2);

                    string dataPart = parts.Length >= 3 ? parts[2] : "";
                    if (parts.Length < 3 && cleanLine.Length > 5)
                        dataPart = cleanLine.Substring(5);

                    dataPart = dataPart.TrimEnd(FIELD_TERMINATOR);
                    ParseSubfields(field, dataPart);
                }
                Fields.Add(field);
            }
        }

        private void ParseSubfields(MarcField field, string dataPart)
        {
            if (string.IsNullOrEmpty(dataPart)) return;

            if (dataPart.Contains(SUBFIELD_MARKER))
            {
                string[] subfields = dataPart.Split(new[] { SUBFIELD_MARKER }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var s in subfields)
                {
                    if (s.Length >= 1)
                        field.Subfields.Add(new MarcSubfield(s[0], s.Substring(1).TrimEnd(FIELD_TERMINATOR)));
                }
            }
            else if (dataPart.Contains('\x1F'))
            {
                string[] subfields = dataPart.Split(new[] { '\x1F' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var s in subfields)
                {
                    if (s.Length >= 1)
                        field.Subfields.Add(new MarcSubfield(s[0], s.Substring(1)));
                }
            }
            else
            {
                for (int k = 0; k < dataPart.Length; k++)
                {
                    if (char.IsLetter(dataPart[k]) && (k == 0 || dataPart[k - 1] == ' ' || dataPart[k - 1] == '^' || dataPart[k - 1] == '\x1F'))
                    {
                        char code = dataPart[k];
                        int next = -1;
                        for (int m = k + 1; m < dataPart.Length - 1; m++)
                        {
                            if (dataPart[m] == ' ' && char.IsLetter(dataPart[m + 1]))
                            {
                                next = m;
                                break;
                            }
                        }

                        string val = next == -1 ? dataPart.Substring(k + 1) : dataPart.Substring(k + 1, next - k - 1);
                        field.Subfields.Add(new MarcSubfield(code, val.Trim()));
                        if (next != -1) k = next;
                        else break;
                    }
                }
            }
        }
        public (bool success, string message) ParseFullMarc(string data)
        {

            System.Text.StringBuilder AlertMessage = new StringBuilder();
            Fields.Clear();
            if (data.Length < 24) return (false, "마크데이터가 24보다 작습니다");

            //리더부는 항상 24바이트이다. 0~23까지이다.    
            Leader = data.Substring(0, 24);
            if (!int.TryParse(Leader.Substring(12, 5), out int baseAddress)) return (false, string.Empty);

            //data Length
            if (int.TryParse(Leader.Substring(0, 5), out int dataLength) == false)
            {
                return (false, "Leader 전체 데이터 길이를 확인할 수 없습니다");
            }






            bool isScaled = false;


            int directoryLength = baseAddress - 24;
            int entryCount = directoryLength / 12;
            var directory = data.Substring(24, directoryLength);

            var RealData = data.Substring(24 + directoryLength);
            if (RealData.Contains((char)0x1D) == false)
            {
                AlertMessage.AppendLine($"레코드식별기호 0x1D 가 없습니다");
            }
            else RealData = RealData.Trim((char)0x1D);

            //태그별식별기호로 분리한다.
            if (RealData[RealData.Length - 1] == (char)0x1E)
                RealData = RealData.Substring(0, RealData.Length - 1);

            var Tags = RealData.Split((char)0x1E);
            if(Tags.Length != entryCount)
            {
                AlertMessage.AppendLine($"디렉토리 카운트({entryCount})와 태그수량({Tags.Length})이 일치하지 않습니다");
            }

            for (int i = 0; i < Math.Min(entryCount,Tags.Length); i++)
            {
                int entryStart = (i * 12);

                //TAG(3)+LENGTH(4)+OFFSET(5)=12
                if (entryStart + 12 > directory.Length) break;
                if (directory[entryStart] == '\x1E' || directory[entryStart] == '^' || directory[entryStart] == FIELD_TERMINATOR) break;

                string tag = directory.Substring(entryStart, 3);
                var tag_len = directory.Substring(entryStart + 3, 4);
                var tag_off = directory.Substring(entryStart + 7, 5);

                if (!int.TryParse(tag_len, out int length))
                {
                    AlertMessage.AppendLine($"태그({tag}) 길이를 확인할 수 없습니다 값:{tag_len}");
                }
                if (!int.TryParse(tag_off, out int offset))
                {
                    AlertMessage.AppendLine($"태그({tag}) 오프셋을 확인할 수 없습니다 값:{offset}");
                }


                string fieldData = Tags[i];
                fieldData = fieldData.TrimEnd('\x1E', '\x1D', FIELD_TERMINATOR, '^', ' ');

                MarcField field = new MarcField(tag);
                if (field.IsControlField)
                    field.ControlValue = fieldData;
                else
                {
                    var subfieldIndex = fieldData.IndexOf((char)0x1f);
                    string fielddata = "";
                    if (subfieldIndex < 0)
                    {
                        //1f가 없는 것은 오류 처리한다.
                        continue;
                    }
                    else if (subfieldIndex < 1)
                    {
                        //지시기호없이 데이터가 시작되는경우이다.
                        field.Indicators = "  ";
                        ParseSubfields(field, fieldData);
                    }
                    else if (subfieldIndex < 2)
                    {
                        //지시기호가1개이다 뒤에 공백을 넣자
                        field.Indicators = fieldData.Substring(0, subfieldIndex) + " ";
                        ParseSubfields(field, fieldData.Substring(1));
                    }
                    else if (subfieldIndex > 2)
                    {
                        //지시기호가 2자리보다 길다? 이건 오류처리하자.
                        field.Indicators = "  ";
                        ParseSubfields(field, fieldData.Substring(subfieldIndex));
                    }
                    else
                    {
                        field.Indicators = fieldData.Substring(0, 2);
                        ParseSubfields(field, fieldData.Substring(2));
                    }

                }
                Fields.Add(field);
            }

            return (true, AlertMessage.ToString());
        }

        public List<T> GetTag<T>(string path)
        {
            if (string.IsNullOrEmpty(path)) return new List<T>();

            string tag = path.Substring(0, 3);
            char? subCode = path.Length > 3 ? (char?)path[3] : null;

            var fields = Fields.Where(f => f.Tag == tag).ToList();
            if (fields.Count == 0) return new List<T>();

            if (typeof(T) == typeof(MarcField))
                return fields.Cast<T>().ToList();

            if (typeof(T) == typeof(MarcSubfield))
            {
                if (!subCode.HasValue) return new List<T>();
                var subResults = new List<MarcSubfield>();
                foreach (var f in fields)
                    subResults.AddRange(f.Subfields.Where(s => s.Code == subCode.Value));
                return subResults.Cast<T>().ToList();
            }

            if (typeof(T) == typeof(string))
            {
                var stringResults = new List<string>();
                foreach (var f in fields)
                {
                    if (f.IsControlField)
                        stringResults.Add(f.ControlValue);
                    else
                    {
                        if (subCode.HasValue)
                            stringResults.AddRange(f.Subfields.Where(s => s.Code == subCode.Value).Select(s => s.Value));
                        else
                            stringResults.AddRange(f.Subfields.Select(s => s.Value));
                    }
                }
                return stringResults.Cast<T>().ToList();
            }

            return new List<T>();
        }

        public List<string> GetTag(string path)
        {
            return GetTag<string>(path);
        }

        public void SetTag(string path, string value, string indicators = "  ")
        {
            if (string.IsNullOrEmpty(path) || path.Length < 3) return;

            string tag = path.Substring(0, 3);
            bool isControl = int.TryParse(tag, out int tagNum) && tagNum < 10;

            var field = Fields.FirstOrDefault(f => f.Tag == tag);
            if (field == null)
            {
                field = new MarcField(tag) { Indicators = indicators };
                Fields.Add(field);
                Fields = Fields.OrderBy(f => f.Tag).ToList();
            }

            if (isControl)
                field.ControlValue = value;
            else
            {
                if (path.Length < 4) throw new ArgumentException("Subfield code required for data fields");
                char subCode = path[3];
                var sub = field.Subfields.FirstOrDefault(s => s.Code == subCode);
                if (sub != null) sub.Value = value;
                else field.Subfields.Add(new MarcSubfield(subCode, value));
            }
        }

        public string Get008Segment(int offset, int length)
        {
            var valLine = GetTag("008").FirstOrDefault();
            if (string.IsNullOrEmpty(valLine) || valLine.Length < offset + length) return string.Empty;
            return valLine.Substring(offset, length);
        }

        public void Set008Segment(int offset, int length, string value)
        {
            var valLine = GetTag("008").FirstOrDefault() ?? new string(' ', 40);
            if (valLine.Length < 40) valLine = valLine.PadRight(40);

            StringBuilder sb = new StringBuilder(valLine);
            for (int i = 0; i < length; i++)
            {
                char c = (i < value.Length) ? value[i] : ' ';
                if (offset + i < sb.Length)
                    sb[offset + i] = c;
            }
            SetTag("008", sb.ToString());
        }

        public string ToMnemonicString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var field in Fields)
                sb.AppendLine(field.ToString());
            return sb.ToString();
        }
    }
}
