// Pngcs is a CSharp implementation of PNG binary reader and writer.
// Copyright (C) 2025 Shuai Zhang <zhangshuai.ustc@gmail.com>
//
// Based on original work:
//   Copyright 2012    Hernán J. González    hgonzalez@gmail.com
//   Licensed under the Apache License, Version 2.0
//
//   You should have received a copy of the Apache License 2.0
//   along with the program.
//   If not, see <http://www.apache.org/licenses/LICENSE-2.0>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace Hjg.Pngcs.Chunks
{
    using System.Collections.Generic;
    using System.Text;

    /// <summary>
    /// All chunks that form an image, read or to be written
    ///
    /// http://www.w3.org/TR/PNG/#table53
    /// </summary>
    ///
    public class ChunksList
    {
        internal const int CHUNK_GROUP_0_IDHR = 0; // required - single
        internal const int CHUNK_GROUP_1_AFTERIDHR = 1; // optional - multiple
        internal const int CHUNK_GROUP_2_PLTE = 2; // optional - single
        internal const int CHUNK_GROUP_3_AFTERPLTE = 3; // optional - multple
        internal const int CHUNK_GROUP_4_IDAT = 4; // required (single pseudo chunk)
        internal const int CHUNK_GROUP_5_AFTERIDAT = 5; // optional - multple
        internal const int CHUNK_GROUP_6_END = 6; // only 1 chunk - requried

        /// <summary>
        ///  Includes all chunks, but IDAT is a single pseudo chunk without data
        /// </summary>
        protected List<PngChunk> chunks;

        internal readonly ImageInfo imageInfo; // only required for writing

        internal ChunksList(ImageInfo imfinfo)
        {
            this.chunks = new List<PngChunk>();
            this.imageInfo = imfinfo;
        }

        /// <summary>
        /// Keys of processed (read or writen) chunks
        /// </summary>
        /// <returns>key:chunk id, val: number of occurrences</returns>
        public Dictionary<string, int> GetChunksKeys()
        {
            Dictionary<string, int> ck = new Dictionary<string, int>();
            foreach (PngChunk c in chunks)
            {
                ck[c.Id] = ck.TryGetValue(c.Id, out int value) ? value + 1 : 1;
            }
            return ck;
        }


        /// <summary>
        /// Returns a copy of the chunk list (but the chunks are not copied)
        /// </summary>
        /// <remarks>This should not be used for general metadata handling
        /// </remarks>
        /// <returns></returns>
        public List<PngChunk> GetChunks()
        {
            return new List<PngChunk>(chunks);
        }

        internal static List<PngChunk> GetXById(List<PngChunk> list, string id, string innerid)
        {
            if (innerid == null)
                return ChunkHelper.FilterList(list, new ChunkPredicateId(id));
            else
                return ChunkHelper.FilterList(list, new ChunkPredicateId2(id, innerid));
        }

        /// <summary>
        /// Adds chunk in next position. This is used only by the pngReader
        /// </summary>
        /// <param name="chunk"></param>
        /// <param name="chunkGroup"></param>
        public void AppendReadChunk(PngChunk chunk, int chunkGroup)
        {
            chunk.ChunkGroup = chunkGroup;
            chunks.Add(chunk);
        }
        /// <summary>
        /// All chunks with this ID
        /// </summary>
        /// <remarks>The GetBy... methods never include queued chunks</remarks>
        /// <param name="id"></param>
        /// <returns>List, empty if none</returns>
        public List<PngChunk> GetById(string id)
        {
            return GetById(id, null);
        }

        /// <summary>
        /// Same as ID, but we an additional discriminator for textual keys
        /// </summary>
        /// <remarks>If innerid!=null and the chunk is PngChunkTextVar or PngChunkSPLT, it's filtered by that id</remarks>
        /// <param name="id"></param>
        /// <param name="innerid">Only used for text and SPLT chunks</param>
        /// <returns>List, empty if none</returns>
        public List<PngChunk> GetById(string id, string innerid)
        {
            return GetXById(chunks, id, innerid);
        }
        /// <summary>
        /// Returns only one chunk
        /// </summary>
        /// <param name="id"></param>
        /// <returns>First chunk found, null if not found</returns>
        public PngChunk GetById1(string id)
        {
            return GetById1(id, false);
        }


        /// <summary>
        /// Returns only one chunk
        /// </summary>
        /// <param name="id"></param>
        /// <param name="failIfMultiple">true, and more than one found: exception</param>
        /// <returns>null if not found</returns>
        public PngChunk GetById1(string id, bool failIfMultiple)
        {
            return GetById1(id, null, failIfMultiple);
        }

        /// <summary>
        /// Sames as <c>GetById1(String id, bool failIfMultiple)</c> but allows an additional innerid
        /// </summary>
        /// <param name="id"></param>
        /// <param name="innerid"></param>
        /// <param name="failIfMultiple">true, and more than one found: exception</param>
        /// <returns>null if not found</returns>
        public PngChunk GetById1(string id, string innerid, bool failIfMultiple)
        {
            List<PngChunk> list = GetById(id, innerid);
            if (list.Count == 0)
                return null;
            if (list.Count > 1 && (failIfMultiple || !list[0].AllowsMultiple()))
                throw new PngjException("unexpected multiple chunks id=" + id);
            return list[list.Count - 1];
        }

        /// <summary>
        /// Finds all chunks "equivalent" to this one
        /// </summary>
        /// <param name="chunk"></param>
        /// <returns>Empty if nothing found</returns>
        public List<PngChunk> GetEquivalent(PngChunk chunk)
        {
            return ChunkHelper.FilterList(chunks, new ChunkPredicateEquiv(chunk));
        }

        /// <summary>
        /// Only the amount of chunks
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "ChunkList: read: " + chunks.Count;
        }
        /// <summary>
        /// Detailed information, for debugging
        /// </summary>
        /// <returns></returns>
        public string ToStringFull()
        {
            StringBuilder sb = new StringBuilder(ToString());
            sb.Append("\n Read:\n");
            foreach (PngChunk chunk in chunks)
            {
                sb.Append(chunk).Append(" G=" + chunk.ChunkGroup + "\n");
            }
            return sb.ToString();
        }

    }
}
