/*
    RawSpeed - RAW file decoder.

    Copyright (C) 2009-2014 Klaus Post
    Copyright (C) 2014 Pedro Côrte-Real
    Copyright (C) 2018 Roman Lebedev

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#pragma once

#include "rawspeedconfig.h"
#include "adt/NORangesSet.h"
#include "tiff/CiffEntry.h"
#include "tiff/CiffTag.h"
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <vector>

namespace rawspeed {

class Buffer;
class ByteStream;
class CiffEntry;
template <typename T> class NORangesSet;

class CiffIFD final {
  CiffIFD* const parent;

  std::vector<std::unique_ptr<const CiffIFD>> mSubIFD;
  std::map<CiffTag, std::unique_ptr<const CiffEntry>> mEntry;

  int subIFDCount = 0;
  int subIFDCountRecursive = 0;

  void recursivelyIncrementSubIFDCount();
  void checkSubIFDs(int headroom) const;
  void recursivelyCheckSubIFDs(int headroom) const;

  // CIFF IFD are tree-like structure, with branches.
  // A branch (IFD) can have branches (IFDs) of it's own.
  // We must be careful to weed-out all the degenerative cases that
  // can be produced e.g. via fuzzing, or other means.
  struct Limits final {
    // How many layers of IFD's can there be?
    // All RPU samples (as of 2018-02-13) are ok with 3.
    // However, let's be on the safe side, and pad it by one.
    static constexpr int Depth = 3 + 1;

    // How many sub-IFD's can this IFD have?
    // NOTE: only for the given IFD, *NOT* recursively including all sub-IFD's!
    // All RPU samples (as of 2018-02-13) are ok with 4.
    // However, let's be on the safe side, and double it.
    static constexpr int SubIFDCount = 4 * 2;

    // How many sub-IFD's can this IFD have, recursively?
    // All RPU samples (as of 2018-02-13) are ok with 6.
    // However, let's be on the safe side, and double it.
    static constexpr int RecursiveSubIFDCount = 6 * 2;
  };

  void add(std::unique_ptr<CiffIFD> subIFD);
  void add(std::unique_ptr<CiffEntry> entry);

  void parseIFDEntry(NORangesSet<Buffer>* valueDatas, ByteStream valueData,
                     ByteStream& dirEntries);

  template <typename Lambda>
  std::vector<const CiffIFD*>
      RAWSPEED_READONLY getIFDsWithTagIf(CiffTag tag, const Lambda& f) const;

  template <typename Lambda>
  const CiffEntry* RAWSPEED_READONLY getEntryRecursiveIf(CiffTag tag,
                                                         const Lambda& f) const;

public:
  explicit CiffIFD(CiffIFD* parent);
  CiffIFD(CiffIFD* parent, ByteStream directory);

  [[nodiscard]] std::vector<const CiffIFD*>
      RAWSPEED_READONLY getIFDsWithTag(CiffTag tag) const;
  [[nodiscard]] std::vector<const CiffIFD*> RAWSPEED_READONLY
  getIFDsWithTagWhere(CiffTag tag, uint32_t isValue) const;
  [[nodiscard]] std::vector<const CiffIFD*> RAWSPEED_READONLY
  getIFDsWithTagWhere(CiffTag tag, const std::string& isValue) const;

  [[nodiscard]] bool RAWSPEED_READONLY hasEntry(CiffTag tag) const;
  [[nodiscard]] bool RAWSPEED_READONLY hasEntryRecursive(CiffTag tag) const;

  [[nodiscard]] const CiffEntry* RAWSPEED_READONLY getEntry(CiffTag tag) const;
  [[nodiscard]] const CiffEntry* RAWSPEED_READONLY
  getEntryRecursive(CiffTag tag) const;
  [[nodiscard]] const CiffEntry* RAWSPEED_READONLY
  getEntryRecursiveWhere(CiffTag tag, uint32_t isValue) const;
  [[nodiscard]] const CiffEntry* RAWSPEED_READONLY
  getEntryRecursiveWhere(CiffTag tag, const std::string& isValue) const;
};

} // namespace rawspeed
