// 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/>.

using System.Collections.Generic;

namespace Hjg.Pngcs.Chunks
{


    /// <summary>Image Metadata, wrapper over a ChunksList</summary>
    /// <remarks>
    /// Additional image info, apart from the ImageInfo and the pixels themselves.
    /// Includes Palette and ancillary chunks.
    /// This class provides a wrapper over the collection of chunks of a image (read or to write) and provides some high
    /// level methods to access them
    /// </remarks>
    public class PngMetadata
    {
        private readonly ChunksList chunkList;
        private readonly bool ReadOnly; // readonly

        internal PngMetadata(ChunksList chunks)
        {
            this.chunkList = chunks;
            if (chunks is ChunksListForWrite)
            {
                this.ReadOnly = false;
            }
            else
            {
                this.ReadOnly = true;
            }
        }

        /// <summary>Queues the chunk at the writer</summary>
        /// <param name="chunk">Chunk, ready for write</param>
        /// <param name="lazyOverwrite">Ovewrite lazily equivalent chunks</param>
        /// <remarks>Warning: the overwriting applies to equivalent chunks, see <c>ChunkPredicateEquiv</c>
        /// and will only make sense for queued (not yet writen) chunks
        /// </remarks>
        public void QueueChunk(PngChunk chunk, bool lazyOverwrite)
        {
            ChunksListForWrite cl = getChunkListW();
            if (ReadOnly)
                throw new PngjException("cannot set chunk : readonly metadata");
            if (lazyOverwrite)
            {
                ChunkHelper.TrimList(cl.GetQueuedChunks(), new ChunkPredicateEquiv(chunk));
            }
            cl.Queue(chunk);
        }

        /// <summary>Queues the chunk at the writer</summary>
        /// <param name="chunk">Chunk, ready for write</param>
        public void QueueChunk(PngChunk chunk)
        {
            QueueChunk(chunk, true);
        }

        private ChunksListForWrite getChunkListW()
        {
            return (ChunksListForWrite)chunkList;
        }

        // ///// high level utility methods follow ////////////

        // //////////// DPI
        /// <summary>
        /// Returns physical resolution, in DPI, in both coordinates
        /// </summary>
        /// <returns>[dpix,dpiy], -1 if not set or unknown dimensions</returns>
        public double[] GetDpi()
        {
            PngChunk c = chunkList.GetById1(ChunkHelper.pHYs, true);
            if (c == null)
                return new double[] { -1, -1 };
            else
                return ((PngChunkPHYS)c).GetAsDpi2();
        }

        /// <summary>
        /// Sets physical resolution, in DPI
        /// </summary>
        /// <remarks>This is a utility method that creates and enqueues a PHYS chunk</remarks>
        /// <param name="dpix">Resolution in x</param>
        /// <param name="dpiy">Resolution in y</param>
        public void SetDpi(double dpix, double dpiy)
        {
            PngChunkPHYS c = new PngChunkPHYS(chunkList.imageInfo);
            c.SetAsDpi2(dpix, dpiy);
            QueueChunk(c);
        }

        /// <summary>
        /// Sets physical resolution, in DPI, both value in x and y dimensions
        /// </summary>
        /// <remarks>This is a utility method that creates and enqueues a PHYS chunk</remarks>
        /// <param name="dpi">Resolution in dpi</param>
        public void SetDpi(double dpi)
        {
            SetDpi(dpi, dpi);
        }

        /// <summary>
        /// Creates a TIME chunk,  <c>nsecs</c> in the past from now.
        /// </summary>
        /// <param name="nsecs">Seconds in the past. If negative, it's a future time</param>
        /// <returns>The created and queued chunk</returns>
        public PngChunkTIME SetTimeNow(int nsecs)
        {
            PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
            c.SetNow(nsecs);
            QueueChunk(c);
            return c;
        }

        /// <summary>
        ///Creates a TIME chunk with current time.
        /// </summary>
        /// <returns>The created and queued chunk</returns>
        public PngChunkTIME SetTimeNow()
        {
            return SetTimeNow(0);
        }

        /// <summary>
        /// Creates a TIME chunk with given date and time
        /// </summary>
        /// <param name="year">Year</param>
        /// <param name="mon">Month (1-12)</param>
        /// <param name="day">Day of month (1-31)</param>
        /// <param name="hour">Hour (0-23)</param>
        /// <param name="min">Minute (0-59)</param>
        /// <param name="sec">Seconds (0-59)</param>
        /// <returns>The created and queued chunk</returns>
        public PngChunkTIME SetTimeYMDHMS(int year, int mon, int day, int hour, int min, int sec)
        {
            PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
            c.SetYMDHMS(year, mon, day, hour, min, sec);
            QueueChunk(c, true);
            return c;
        }

        /// <summary>
        /// Gets image timestamp, TIME chunk
        /// </summary>
        /// <returns>TIME chunk, null if not present</returns>
        public PngChunkTIME GetTime()
        {
            return (PngChunkTIME)chunkList.GetById1(ChunkHelper.tIME);
        }

        /// <summary>
        /// Gets image timestamp, TIME chunk, as a String
        /// </summary>
        /// <returns>Formated TIME, empty string if not present</returns>
        public string GetTimeAsString()
        {
            PngChunkTIME c = GetTime();
            return c == null ? "" : c.GetAsString();
        }

        // //////////// TEXT

        /// <summary>
        /// Creates a text chunk and enqueues it
        /// </summary>
        /// <param name="key">Key. Short and ASCII string</param>
        /// <param name="val">Text.</param>
        /// <param name="useLatin1">Flag. If false, will use UTF-8 (iTXt)</param>
        /// <param name="compress">Flag. Uses zTXt chunk.</param>
        /// <returns>The created and enqueued chunk</returns>
        public PngChunkTextVar SetText(string key, string val, bool useLatin1, bool compress)
        {
            if (compress && !useLatin1)
                throw new PngjException("cannot compress non latin text");
            PngChunkTextVar c;
            if (useLatin1)
            {
                if (compress)
                {
                    c = new PngChunkZTXT(chunkList.imageInfo);
                }
                else
                {
                    c = new PngChunkTEXT(chunkList.imageInfo);
                }
            }
            else
            {
                c = new PngChunkITXT(chunkList.imageInfo);
                ((PngChunkITXT)c).SetLangtag(key); // we use the same orig tag (this is not quite right)
            }
            c.SetKeyVal(key, val);
            QueueChunk(c, true);
            return c;
        }

        /// <summary>
        /// Creates a plain text chunk (tEXT) and enqueues it
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="val">Text</param>
        /// <returns>The created and enqueued chunk</returns>
        public PngChunkTextVar SetText(string key, string val)
        {
            return SetText(key, val, false, false);
        }

        /// <summary>
        /// Retrieves all text chunks with a given key
        /// </summary>
        /// <param name="key">Key</param>
        /// <returns>Empty list if nothing found</returns>
        /// <remarks>Can mix tEXt zTXt and iTXt chunks</remarks>
        public List<PngChunkTextVar> GetTxtsForKey(string key)
        {
            List<PngChunkTextVar> li = new List<PngChunkTextVar>();
            foreach (PngChunk c in chunkList.GetById(ChunkHelper.tEXt, key))
                li.Add((PngChunkTextVar)c);
            foreach (PngChunk c in chunkList.GetById(ChunkHelper.zTXt, key))
                li.Add((PngChunkTextVar)c);
            foreach (PngChunk c in chunkList.GetById(ChunkHelper.iTXt, key))
                li.Add((PngChunkTextVar)c);
            return li;
        }

        /// <summary>
        /// Joins all strings for a given key
        /// </summary>
        /// <param name="key">Key</param>
        /// <returns>Concatenated (with newlines) if several found, empty string if none</returns>
        /// <remarks>You'd perhaps prefer GetTxtsForKey</remarks>
        public string GetTxtForKey(string key)
        {
            string t = "";
            List<PngChunkTextVar> li = GetTxtsForKey(key);
            if (li.Count == 0)
                return t;
            foreach (PngChunkTextVar c in li)
                t = t + c.GetVal() + "\n";
            return t.Trim();
        }

        public PngChunkPLTE GetPLTE()
        {
            return (PngChunkPLTE)chunkList.GetById1(PngChunkPLTE.ID);
        }

        public PngChunkPLTE CreatePLTEChunk()
        {
            PngChunkPLTE plte = new PngChunkPLTE(chunkList.imageInfo);
            QueueChunk(plte);
            return plte;
        }

        /**
        * Returns the TRNS chunk, if present
        *
        * @return null if not present
        */
        public PngChunkTRNS GetTRNS()
        {
            return (PngChunkTRNS)chunkList.GetById1(PngChunkTRNS.ID);
        }

        /**
        * Creates a new empty TRNS chunk, queues it for write and return it to the caller, who should fill its entries
        */
        public PngChunkTRNS CreateTRNSChunk()
        {
            PngChunkTRNS trns = new PngChunkTRNS(chunkList.imageInfo);
            QueueChunk(trns);
            return trns;
        }
    }
}
