/*
 * (C) Copyright 2013 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#pragma once

#include <iosfwd>
#include <string>

#include "eckit/config/Parametrisation.h"
#include "eckit/serialisation/FileStream.h"

#include "atlas/library/config.h"
#include "atlas/util/Config.h"
#include "atlas/util/Object.h"
#include "atlas/util/ObjectHandle.h"

namespace eckit {
class Parametrisation;
class PathName;
}  // namespace eckit

namespace atlas {
class Mesh;
namespace mesh {
namespace detail {
class MeshImpl;
}
}  // namespace mesh
}  // namespace atlas

namespace atlas {
class Field;
class FieldSet;
namespace field {
class FieldImpl;
class FieldSetImpl;
}  // namespace field
}  // namespace atlas

namespace atlas {
class FunctionSpace;
namespace functionspace {
class FunctionSpaceImpl;
}
}  // namespace atlas

namespace atlas {
namespace output {

using PathName = eckit::PathName;

// -----------------------------------------------------------------------------

namespace detail {
class OutputImpl : public util::Object {
public:
    typedef atlas::util::Config Parameters;

public:
    OutputImpl();

    virtual ~OutputImpl();

    /// Write mesh file
    virtual void write( const Mesh&, const eckit::Parametrisation& = util::NoConfig() ) const = 0;

    /// Write field to file
    virtual void write( const Field&, const eckit::Parametrisation& = util::NoConfig() ) const = 0;

    /// Write fieldset to file using FunctionSpace
    virtual void write( const FieldSet&, const eckit::Parametrisation& = util::NoConfig() ) const = 0;

    /// Write field to file using Functionspace
    virtual void write( const Field&, const FunctionSpace&,
                        const eckit::Parametrisation& = util::NoConfig() ) const = 0;

    /// Write fieldset to file using FunctionSpace
    virtual void write( const FieldSet&, const FunctionSpace&,
                        const eckit::Parametrisation& = util::NoConfig() ) const = 0;
};

}  // namespace detail

class Output : DOXYGEN_HIDE( public util::ObjectHandle<detail::OutputImpl> ) {
public:
    using Handle::Handle;
    Output() = default;
    Output( const std::string&, std::ostream&, const eckit::Parametrisation& = util::NoConfig() );

    /// Write mesh file
    void write( const Mesh&, const eckit::Parametrisation& = util::NoConfig() ) const;

    /// Write field to file
    void write( const Field&, const eckit::Parametrisation& = util::NoConfig() ) const;

    /// Write fieldset to file using FunctionSpace
    void write( const FieldSet&, const eckit::Parametrisation& = util::NoConfig() ) const;

    /// Write field to file using Functionspace
    void write( const Field&, const FunctionSpace&, const eckit::Parametrisation& = util::NoConfig() ) const;

    /// Write fieldset to file using FunctionSpace
    void write( const FieldSet&, const FunctionSpace&, const eckit::Parametrisation& = util::NoConfig() ) const;
};

namespace detail {

class OutputFactory {
public:
    /*!
   * \brief build Output with factory key, and default options
   * \return mesh generator
   */
    static const OutputImpl* build( const std::string&, std::ostream& );

    /*!
   * \brief build Output with factory key inside parametrisation,
   * and options specified in parametrisation as well
   * \return mesh generator
   */
    static const OutputImpl* build( const std::string&, std::ostream&, const eckit::Parametrisation& );

    /*!
   * \brief list all registered mesh generators
   */
    static void list( std::ostream& );

private:
    std::string name_;
    virtual const OutputImpl* make( std::ostream& )                                = 0;
    virtual const OutputImpl* make( std::ostream&, const eckit::Parametrisation& ) = 0;

protected:
    OutputFactory( const std::string& );
    virtual ~OutputFactory();
};

template <class T>
class OutputBuilder : public OutputFactory {
    virtual const OutputImpl* make( std::ostream& stream ) { return new T( stream ); }
    virtual const OutputImpl* make( std::ostream& stream, const eckit::Parametrisation& param ) {
        return new T( stream, param );
    }

public:
    OutputBuilder( const std::string& name ) : OutputFactory( name ) {}
};

// -----------------------------------------------------------------------------

extern "C" {
void atlas__Output__delete( OutputImpl* This );
const OutputImpl* atlas__Output__create( const char* factory_key, std::ostream* stream,
                                         const eckit::Parametrisation* params );
void atlas__Output__write_mesh( const OutputImpl* This, mesh::detail::MeshImpl* mesh,
                                const eckit::Parametrisation* params );
void atlas__Output__write_fieldset( const OutputImpl* This, const field::FieldSetImpl* fieldset,
                                    const eckit::Parametrisation* params );
void atlas__Output__write_field( const OutputImpl* This, const field::FieldImpl* field,
                                 const eckit::Parametrisation* params );
void atlas__Output__write_fieldset_fs( const OutputImpl* This, const field::FieldSetImpl* fieldset,
                                       const functionspace::FunctionSpaceImpl* functionspace,
                                       const eckit::Parametrisation* params );
void atlas__Output__write_field_fs( const OutputImpl* This, const field::FieldImpl* field,
                                    const functionspace::FunctionSpaceImpl* functionspace,
                                    const eckit::Parametrisation* params );
}

}  // namespace detail
}  // namespace output
}  // namespace atlas
