// Copyright 2019 Diffuse Logic, all rights reserved.

#ifndef BELLA_SDK_ENGINE_H
#define BELLA_SDK_ENGINE_H

#include "bella_scene.h"
#include "../dl_core/dl_licensing.h"

namespace dl {
namespace bella_sdk {

//=================================================================================================
// Progress.
// 
// This class is used in the EngineObserver::onProgress override. It has now lived through a few
// different solvers and is a pretty big mess in need of refactoring. You can ignore most of it
// and just do something like this in your onProgress:
// 
//   void onProgress(String pass, Progress progress)
//   {
//       _progressLevel = progress.level();
//       _progressValue = progress.progress();
//       logMessage(progress.toString());
//   }
// 
//=================================================================================================

class DL_API Progress DL_FINAL : public RefersTo<IRefCounted>
{
public:
    enum Type
    {
        Type_Generic,
        Type_BeautyPass,
        Type_ExtraPass
    };

    Progress();
    Progress( IRefCounted* impl ) : RefersTo( impl ) { }

    Progress( String info, Real progress );
    Progress( String info, Real progress, UInt elapsed, UInt benchmark, Int targetNoise );
    Progress( String info, Real progress, UInt level, UInt elapsed, UInt remainingLevel, UInt remainingEnd, UInt benchmark );

    Type                        DL_STDCALL type() const;
    String                      DL_STDCALL info() const;
    Real                        DL_STDCALL progress() const;

    // For Type_BeautyPass.
    //
    UInt                        DL_STDCALL level() const;
    UInt                        DL_STDCALL elapsed() const;
    String                      DL_STDCALL elapsedToString() const;
    UInt                        DL_STDCALL remainingLevel() const;
    UInt                        DL_STDCALL remainingEnd() const;
    String                      DL_STDCALL remainingToString() const;
    String                      DL_STDCALL remainingLevelToString() const;
    UInt                        DL_STDCALL benchmark() const;
    Int                         DL_STDCALL targetNoise() const;

    // Universal.
    //
    String                      DL_STDCALL toString() const;
};

//=================================================================================================
// EngineObserver.
// 
// Inherit from this class and subscribe to the engine to receive notifications. In the onImage
// override, do not take a reference from the passed Image, since it does not own its memory, and
// will become invalid; rather, take a copy of the image.
//=================================================================================================

struct DL_API EngineObserver
{
    virtual void                DL_STDCALL onSceneLoaded( Scene scene )                    { DL_UNUSED( scene ); }
    virtual void                DL_STDCALL onSceneUnloaded( Scene scene )                  { DL_UNUSED( scene ); }
    virtual void                DL_STDCALL onStarting()                                    { }
    virtual void                DL_STDCALL onStarted( String pass )                        { DL_UNUSED( pass ); }
    virtual void                DL_STDCALL onStatus( String pass, String msg )             { DL_UNUSED( pass ); DL_UNUSED( msg ); }
    virtual void                DL_STDCALL onError( String pass, String msg )              { DL_UNUSED( pass ); DL_UNUSED( msg ); }
    virtual void                DL_STDCALL onProgress( String pass, Progress progress )    { DL_UNUSED( pass ); DL_UNUSED( progress ); }
    virtual void                DL_STDCALL onImage( String pass, Image image )             { DL_UNUSED( pass ); DL_UNUSED( image ); }
    virtual void                DL_STDCALL onStopping( String pass )                       { DL_UNUSED( pass ); }
    virtual void                DL_STDCALL onStopped( String pass = "" )                   { DL_UNUSED( pass ); }
    virtual void                DL_STDCALL doEvents()                                      { }

    virtual ~EngineObserver() = default;
};

//=================================================================================================
// Engine.
//=================================================================================================

class DL_API Engine DL_FINAL : public RefersTo<IRefCounted>
{
public:
    Engine();

    // Subscribe & unsubscribe observers from this engine.
    //
    Bool                        DL_STDCALL subscribe( EngineObserver* observer );
    Bool                        DL_STDCALL unsubscribe( EngineObserver* observer );
    Bool                        DL_STDCALL unsubscribeAll();

    // Engine is internally a SceneObserver, so these subscribe & unsubscribe the engine from the
    // given Scene. Usually not necessary to use this, it is used in Bella to handle the slightly
    // confusing situation of the material editor preview, where we have two engines, the normal
    // IPR, and also the material IPR, where changes to a node must update both of those engines.
    //
    Bool                        DL_STDCALL subscribeTo( Scene scene );
    Bool                        DL_STDCALL unsubscribeFrom( Scene scene );

    // Restrict the engine to reacting to changes to a set of nodes. This is also used in the GUI
    // material editor, to have the preview IPR listen only to the current material and its
    // dependencies.
    //
    void                        DL_STDCALL setFilter( NodeVector nodesToWatch = {} );

    // The Engine starts out with its own scene, and usually you can just use this one.
    //
    Scene                       DL_STDCALL scene();

    // But you can also set the scene we are currently rendering in this Engine. Passing the
    // default-constructed Scene() will clear everything out. These are more useful for creating
    // an interactive renderer, where an engine instance is long-lived, than a one-shot CLI.
    //
    Bool                        DL_STDCALL loadScene( Scene scene = Scene() );
    Bool                        DL_STDCALL loadScene( String path, SceneProgress* progress = nullptr );

    // Once you have set up your scene, loaded your node definitions, and whatever else you wish,
    // you simply call start(). The engine will start rendering, and any subscribed observers will
    // be given notifications.
    //
    Bool                        DL_STDCALL start();

    // Stop or abort rendering. Abort stops much faster, as it assumes you don't care about the
    // state of the engine afterward. So generally, wait for stop(true) if you need the rendered
    // image to be saved, call abort(false) if you need the engine to quit asap and will not be
    // touching it while it is aborting, and call abort(true) if you are aborting to make some
    // change prior to starting the engine again.
    //
    Bool                        DL_STDCALL stop( Bool block = true, Bool abort = false );
    Bool                        DL_STDCALL abort( Bool block = true );

    // Any time the engine is enabled, this will return true. This is different than rendering() in
    // that when a render reaches the end, it is no longer rendering(), but it will begin rendering
    // again immediately upon receiving a change to the scene.
    //
    Bool                        DL_STDCALL running() const;

    // In some situations it is useful to be able to pause the engine without disturbing anything.
    // Calling pause(true) will block until the engine has stopped, and the engine will then no
    // longer be enabled (i.e. running() will now return false). Calling resume will restart the
    // engine if it is currently paused. Setting abort=true will stop the engine faster, but will
    // skip notification of any image currently being rendered.
    //
    Bool                        DL_STDCALL pause( Bool block = false, Bool abort = true );
    Bool                        DL_STDCALL paused() const;
    Bool                        DL_STDCALL resume();

    // Rendering will return true after calling start(), and until the current image has finished
    // rendering, or has been stopped.
    //
    Bool                        DL_STDCALL rendering() const;

    // These will return the current stopping/aborting/stopped state of the engine.
    //
    Bool                        DL_STDCALL aborting() const;
    Bool                        DL_STDCALL stopping() const;
    Bool                        DL_STDCALL stopped() const;

    // If an internal error occurred, this will return true. Generally there is nothing to do about
    // this except fix whatever is wrong and call start() again.
    //
    Bool                        DL_STDCALL failed() const;

    // Static helper for checking a mesh or meshes for validity. Returns the empty string if the
    // mesh or meshes check out OK, and otherwise returns status messages for each mesh.
    //
    static String               DL_STDCALL checkMesh( Node mesh );
    static StringVector         DL_STDCALL checkMeshes( NodeVector meshes );

    // After translating a mesh from native 3d platform format to Bella, you can call this method
    // to erase repeated vertexes and apply other Bella mesh optimizations.
    //
    static Bool                 DL_STDCALL optimizeMesh( Node mesh );
    static Bool                 DL_STDCALL optimizeMeshes( NodeVector meshes );
    
    // Rarely used, this method re-renders a pass, or resumes it if possible. The method will not
    // update anything but attributes in the pass, so it will not reflect any other changes made
    // to the scene since the end of the prior rendering. Basically, you can ignore its existence.
    //
    Bool                        DL_STDCALL reRender( String pass );

    // These are various methods added to support GUI features, and they need cleaning up, so use
    // them at your own risk. We are not going to document them any further than this for now.
    //
    Float                       DL_STDCALL pickCameraDistance( int xMouse, int yMouse );
    Node                        DL_STDCALL pickMaterial( int xMouse, int yMouse );
    Node                        DL_STDCALL pickInstance( int xMouse, int yMouse );
    UInt64                      DL_STDCALL pickInstanceAndMaterial( Node& nodeTransform, Node& nodeMaterial, String& nameIntersector, int xMouse, int yMouse );
    Bool                        DL_STDCALL generateRay( Vec3f& origin, Vec3f& direction, int xMouse, int yMouse );
    Float                       DL_STDCALL projectNearestRayPointToPivotAxis( Vec3f pivot, Vec3f axis, int xMouse, int yMouse );
    void                        DL_STDCALL instancesBoundingBoxes( Vec3f& min, Vec3f& max, ds::UInt64Vector hashes );
    void                        DL_STDCALL translate( Vec3f translationLocal, Float scale, ds::UInt64Vector hashes );

    // If you are creating an interactive render window, you should call enableInteractiveMode().
    // When interactive mode is disabled, the engine will not respond to any scene changes, and
    // other optimizations may be applied internally.
    //
    void                        DL_STDCALL enableInteractiveMode();
    void                        DL_STDCALL disableInteractiveMode();
    Bool                        DL_STDCALL interactiveModeEnabled() const;

    // For an interactive render window, it is often most convenient to have the engine apply
    // the current display transform color space automatically. However, not always; for instance
    // maya wants to apply its own transform, so we have to disable this.
    //
    void                        DL_STDCALL enableDisplayTransform();
    void                        DL_STDCALL disableDisplayTransform();
    Bool                        DL_STDCALL displayTransformEnabled() const;

    // The engine can watch for changes (during IPR) and automatically update references to files
    // when the files referenced are changed or removed on disk. This is disabled by default.
    //
    void                        DL_STDCALL enableFileWatcher();
    void                        DL_STDCALL disableFileWatcher();
    Bool                        DL_STDCALL fileWatcherEnabled() const;

    // Request the image for a pass to be saved to disk at the given bits per channel and color
    // space.
    //
    Bool                        DL_STDCALL writeImage( String pass, String outputPath, UInt bpc, String colorSpace );

    // Get the current engine version & build date.
    //
    static Version              DL_STDCALL version();
    static UInt                 DL_STDCALL buildDate();

    // Licensing functions. The engine automatically handles licensing, but you can use these
    // methods to force it to refresh, in case you have added/removed a license file. You can use
    // the licenseText argument to licenseInfo() to directly check a given license text.
    //
    static Bool                 DL_STDCALL isLicensed( Bool checkDisk = false );
    static String               DL_STDCALL licenseType( Bool checkDisk = false );
    static licensing::Info      DL_STDCALL licenseInfo( String licenseText = "" );

    // Get a string containing info about third party open source licenses used in Bella.
    //
    static String               DL_STDCALL thirdPartyLicenseInfo();
};

} // bella_sdk
} // dl

#endif // BELLA_SDK_ENGINE_H
