/************************************************************************
 *
 * Copyright (C) 2009-2025 IRCAD France
 * Copyright (C) 2012-2020 IHU Strasbourg
 *
 * This file is part of Sight.
 *
 * Sight 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 3 of the License, or
 * (at your option) any later version.
 *
 * Sight 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 Sight. If not, see <https://www.gnu.org/licenses/>.
 *
 ***********************************************************************/

#pragma once

#include <sight/io/__/config.hpp>

#include "io/__/service/io_types.hpp"

#include <core/tools/failed.hpp>

#include <data/string.hpp>

#include <service/base.hpp>

#include <filesystem>

namespace sight::io::service
{

/**
 * @brief Reader service API. It manages extension points definition and extension configuration
 *
 * This abstract class defines the API for all reader services. It provides the basic methods to read data from files
 * or a folder. The reader service can be configured using properties. Subclasses should implement the
 * open_location_dialog(), get_path_type() and get_supported_extensions() methods. In updating(), which should perform
 * the "read" and updating the m_data object, m_read_failed must be set to the correct value.
 *
 * @section Slots Slots
 * - \b open_location_dialog() : Open a dialog to select a file or a folder.
 *
 * @subsection In-Out In-Out
 * - \b data [sight::data::object]: Generic data inout. The reader should puplate this data with the read data.
 *
 * @subsection Properties Properties
 * - \b window_title (optional) : The window title that can be used for open_location_dialog. This abstract class
 *                   defines a default that can be overriden by calling the appropriate constructor, but the XML
 *                   property definition have the precedence in all cases.
 * - \b files : The file(s) to open. Depending of the path_type_t, it can be a single file or multiple files.
 * - \b folder : The folder to open. Used when the path_type_t is "folder".
 * - \b resources : When files / folder to open are resources and need to be found in the module or libraries.
 */

class SIGHT_IO_CLASS_API reader : public sight::service::base
{
public:

    SIGHT_DECLARE_SERVICE(reader, sight::service::base);

    /// Enum to define a password policy
    enum class dialog_policy : uint8_t
    {
        never   = 0,  /// Never show the dialog
        once    = 1,  /// Show only once, store the location as long as the service is started
        always  = 2,  /// Always show the location dialog
        invalid = 255 /// Used for error management
    };

    /**
     * @name Slots API
     * @{
     */
    struct slots
    {
        using key_t = sight::core::com::slots::key_t;
        static inline const key_t OPEN_LOCATION_DIALOG     = "open_location_dialog";
        static inline const key_t UPDATE_DEFAULT_LOCATIONS = "update_default_locations";
    };
    //@}

    /**
     * @name    Specific service methods for reading
     */
    //@{

    /**
     * @brief Configure the image path (by default does nothing).
     *
     * This method is used to find the file path  using a file selector.
     */
    SIGHT_IO_API virtual void open_location_dialog() = 0;

    /**
     * @brief   returns  (filename) extension
     */
    SIGHT_IO_API virtual std::vector<std::string> get_supported_extensions();

    /**
     * @brief This method must be implemented by concrete service readers
     * that use path file system to read data.
     *
     * This method returns supported path format (file, files or folder).
     * A reader can support file and folder, or files and folder, but not
     * file and files ( because files include file concept ).
     */
    SIGHT_IO_API virtual io::service::path_type_t get_path_type() const;

    /**
     * @brief Returns the file path set by the user or set during service configuration
     * @pre exception if a file path is not defined  ( m_locations.empty() )
     * @pre exception if service does not support FILE mode
     */
    SIGHT_IO_API const std::filesystem::path& get_file() const;

    /**
     * @brief Sets file path
     * @pre exception if service does not support FILE mode
     */
    SIGHT_IO_API void set_file(const std::filesystem::path& _file);

    /**
     * @brief Returns file paths set by the user or set during service configuration
     * @pre exception if a file path is not defined ( m_locations.empty() )
     * @pre exception if service does not support FILES mode
     */
    SIGHT_IO_API const io::service::locations_t& get_files() const;

    /**
     * @brief Sets file paths
     * @pre exception if service does not support FILES mode
     */
    SIGHT_IO_API void set_files(const io::service::locations_t& _files);

    /**
     * @brief Returns folder path set by the user or set during service configuration
     * @pre exception if a folder path is not defined ( m_locations.empty() )
     * @pre exception if service does not support FOLDER mode
     */
    SIGHT_IO_API const std::filesystem::path& get_folder() const;

    /**
     * @brief Sets folder path
     * @pre exception if service does not support FOLDER mode
     */
    SIGHT_IO_API void set_folder(const std::filesystem::path& _folder);

    /**
     * @brief Clear any location set by the set_file/set_files/set_folder setter
     */
    SIGHT_IO_API void clear_locations();

    /**
     * @brief Returns file/files/folder paths set by the user or set during service configuration
     * @pre exception if a file path is not defined ( m_locations.empty() )
     */
    SIGHT_IO_API const io::service::locations_t& get_locations() const;

    /// Returns if a location has been defined ( by the configuration process or directly by user )
    SIGHT_IO_API bool has_location_defined() const;

    /// Returns if reading has failed.
    SIGHT_IO_API bool has_failed() const;

    //@}

    /// Convenience function to convert from DialogPolicy enum value to string
    constexpr static std::string_view dialog_policy_to_string(dialog_policy _policy) noexcept
    {
        switch(_policy)
        {
            case dialog_policy::once:
                return "once";

            case dialog_policy::always:
                return "always";

            default:
                return "never";
        }
    }

    /// Convenience function to convert from string to DialogPolicy enum value
    constexpr static dialog_policy string_to_dialog_policy(std::string_view _policy) noexcept
    {
        if(constexpr auto never = dialog_policy_to_string(dialog_policy::never);
           _policy == never || _policy.empty() || _policy == "default")
        {
            return dialog_policy::never;
        }

        if(constexpr auto once = dialog_policy_to_string(dialog_policy::once); _policy == once)
        {
            return dialog_policy::once;
        }

        if(constexpr auto always = dialog_policy_to_string(dialog_policy::always); _policy == always)
        {
            return dialog_policy::always;
        }

        // Error case
        return dialog_policy::invalid;
    }

protected:

    SIGHT_IO_API reader(const std::string& _default_window_title = s_DEFAULT_WINDOW_TITLE) noexcept;

    SIGHT_IO_API ~reader() noexcept override = default;

    /**
     * @brief This method proposes to parse xml configuration to retrieve file/files/folder paths.
     * @warning Using XML configuration is deprecated, use the properties instead.
     */
    SIGHT_IO_API void configuring() override;

    /// Defines the auto-connection between the file/folder properties and the 'set_file/set_folder'
    SIGHT_IO_API connections_t auto_connections() const override;

    /// Defines whether reading was performed correctly, or if it has failed or if user has cancelled the process.
    bool m_read_failed {false};

    /// Generic output data
    data::ptr<data::object, data::access::inout> m_data {this, sight::io::service::DATA_KEY};

    /// Window title for the file dialog
    sight::data::property<sight::data::string> m_window_title {this, WINDOW_TITLE_KEY, s_DEFAULT_WINDOW_TITLE};

private:

    /**
     * @brief SLOT: Reads values from the file / folder properties and updates m_locations.
     */
    void update_default_locations();

    /**
     * @brief fill m_locations
     *
     * @param _files
     * @param _folder
     * @param _resources
     */
    void update_locations(
        const io::service::locations_t& _files,
        const std::filesystem::path& _folder,
        const std::vector<std::string>& _resources
    );

    /// Default window title
    inline static const std::string s_DEFAULT_WINDOW_TITLE = "Choose a file";

    /// Value to stock file or folder paths
    io::service::locations_t m_locations;

    /// Default values for file/folder paths
    sight::data::property<sight::data::string> m_files {this, FILES_KEY, std::string()};
    sight::data::property<sight::data::string> m_folder {this, FOLDER_KEY, std::string()};
    sight::data::property<sight::data::string> m_resources {this, RESOURCES_KEY, std::string()};
};

} //namespace sight::io::service
