// Copyright (c) 2021 The Foundry Visionmongers Ltd. All rights reserved.

// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//     * Neither the name of Foundry nor the names of its
//       contributors may be used to endorse or promote products derived from this
//       software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// This work is a C++ implementation compatible with version 1.2.8 of the Python-based Nuke gizmo
// authored at Psyop by Jonah Friedman and Andy Jones (see https://github.com/Psyop/Cryptomatte).

#ifndef CRYPTOMATTE_PLUGIN_H
#define CRYPTOMATTE_PLUGIN_H

#include "CryptomatteUtils.h"
#include "CryptomattePickingManager.h"
#include "DDImage/Enumeration_KnobI.h"
#include "DDImage/Knobs.h"
#include "DDImage/PixelIop.h"
#include <functional>

namespace Foundry {
  namespace NukePlugins {

    class CryptomattePlugin : public DD::Image::PixelIop, public Cryptomatte::PickingInterface
    {
    public:
      CryptomattePlugin(Node* node);

      static const Description kDescription;
      const char* Class() const override;
      const char* node_help() const override;

      void in_channels(int input, DD::Image::ChannelSet& mask) const override;
      void knobs(DD::Image::Knob_Callback callback) override;
      int knob_changed(DD::Image::Knob* k) override;
      bool updateUI(const DD::Image::OutputContext& context) override;

      // Picking interface functions
      Cryptomatte::CryptomatteObject getSelectedCryptomatteObject() const override;
      Cryptomatte::Manifest getManifestFromSelectedSource(const Cryptomatte::CryptomatteObject& cryptomatte) override;
      Cryptomatte::PickingInterface::PixelRowFetcher getCryptomattePixelRowFetcher() const override;

    protected:
      void _validate(bool forReal) override;
      void pixel_engine(const DD::Image::Row& in, int y, int x, int r, DD::Image::ChannelMask channels, DD::Image::Row& out) override;

    private:
      // Possible sources of the Cryptomatte Manifest
      enum class ManifestSource {
        MetadataEmbedded = 0, //< Manifest is embedded in the metadata.
        MetadataSidecar,      //< Manifest source is a sidecar. The path to the file is provided in the metadata.
        MetadataAuto,         //< Manifest source is a sidecar, if the path to the file is provided in the metadata;
                              //  otherwise embedded.
        CustomSidecar         //< Manifest source is a sidecar. The path to the file is provided by the user
                              //  in the Sidecar Filepath knob.
      };

      // Helper struct that contains Cryptomatte objects, fetched from the metadata,
      // the index of currently selected object, and the flag that determines whether the UI
      // is up to date in regards to fetched objects.
      struct CryptomatteContext {
        std::vector<Cryptomatte::CryptomatteObject> objects;
        size_t selectedIndex = 0;
        bool cryptoLayersUiInvalid = true;
        bool manifestSourceUiInvalid = true;
      };

      // Fetches available Cryptomatte objects from the metadata and determines the index
      // of selected object as well as whether the UI is up to date in regards to fetched objects.
      CryptomatteContext loadCryptomatteContext() const;

      void saveLastSelectedCryptoLayerName(const CryptomatteContext& objectsAndState);

      // Updates the value and the Enabled flag of the Sidecar Filepath knob,
      // depending on the selected manifest source.
      void updateSidecarFilepathKnob(const CryptomatteContext& cryptomatteContext);

      // Copies the filepath from the sidecar file knob to the custom sidecar knob,
      // if the selected Manifest Source is Custom Sidecar.
      void saveCustomSidecar();

      // Populates Cryptomatte Layers menu in regards to the state of selected Cryptomatte Object.
      void updateCryptoLayerChoiceKnob(const CryptomatteContext& objectsAndState);

      // Adds obsolete knobs for backward compatibility & error handling when loading a Cryptomatte gizmo.
      void addObsoleteKnobs(DD::Image::Knob_Callback f) const;

      // Helper method that returns an interface to enumeration knob by the given name.
      // nullptr is returned if the enumeration konb isn't found.
      DD::Image::Enumeration_KnobI* enumerationKnobI(const char* name) const;

      // Knob names
      static const char* kCryptoLayerChoiceKnob;
      static const char* kManifestSourceKnob;
      static const char* const kManifestSourceOptions[];
      static const char* kSidecarFilepathKnob;
      static const char* kCustomSidecarKnob;
      static const char* kMatteOutputKnob;
      static const char* kUnpremultiply;
      static const char* kRemoveChannelsKnob;
      static const char* kLastSelectedCryptoLayerNameKnob;

      Cryptomatte::PickingManager _picker;

      // Index of user-selected cryptomatte that persists
      // in a collection of available cryptomattes
      int _selectedCryptomatteIndex = 0;

      // Menu items of the Cryptomatte Layers enumeration knob
      DD::Image::ENUM_LABELS _cryptoLayersMenu = nullptr;

      // Index of manifest source.
      int _manifestSourceIndex = static_cast<int>(ManifestSource::MetadataAuto);

      // Path to the sidecar manifest file, either custom or from metadata,
      // displayed on the properties pannel.
      //
      // Note: The value is dynamic and depends on the state of Manifest Source enumeration knob.
      // It is used for displaying purposes and is not stored with the script.
      const char* _sidecarFilePath = nullptr;

      // Path to the custom sidecar manifest file, if the user provided any.
      std::string _customSidecar;

      // Determines if the output must be unpremultiplied by the input alpha
      bool _unpremultiplyEnabled = false;

      // User-selected Matte Out Channel
      DD::Image::Channel _matteOutChannel = DD::Image::Chan_Alpha;

      // Determines if all the channels except for RGB
      // and the Matte Output channel should be removed.
      bool _removeChannels = false;

      // Determines if the manifest source was modified by the user.
      // This flag keeps the sidecar file path knob from being automatically updated
      // when the sidecar filepath changes in the upstream metadata.
      bool _manifestSourceModified = false;

      // Preserves the name of last user-selected cryptomatte.
      std::string _lastSelectedCryptoLayerName;

      // Dynamic values, updated by each _validate() call
      // Cryptomatte IDs, resolved from the user-selected Matte List
      std::set<float> _resolvedIDs;

      // User-selected cryptomatte object
      Cryptomatte::CryptomatteObject _selectedCryptomatte;
    };

  }
}

#endif