/***************************************************************
 *
 * Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/


#ifndef SHADOW_H
#define SHADOW_H

#include "condor_common.h"
#include "baseshadow.h"
#include "remoteresource.h"
#include "dc_coroutines.h"

#include <optional>
#include "guidance.h"
#include "catalog_utils.h"
#include "condor_holdcodes.h"

class ShadowHookMgr;

/** This class is the implementation for the shadow.  It is
	called UniShadow because:
	<ul>
	 <li>It's not named CShadow (sorry, Todd).
	 <li>It deals with <b>one</b> remote host.
	 <li>Calling it 'Shadow' isn't good, with all the Shadow flavors.
	 <li>I suck at naming things.
	</ul>

	<p>
	Much of the base functionality of Shadowness can be found in
	BaseShadow.  This class uses one instance of RemoteResource to
	represent the remote job.

	<p>
	Based heavily on code by Todd Tannenbaum.
	@see RemoteResource
	@author Mike Yoder
*/
class UniShadow : public BaseShadow
{
 public:

		/// Constructor.  Only makes a new RemoteResource.
	UniShadow();

		/// Destructor, it's virtual
	virtual ~UniShadow();

		/** This is the init() method that gets called upon startup.

			Does the following:
			<ul>
			 <li>Checks some parameters
			 <li>Sets up the remote Resource
			 <li>Calls BaseShadow::init()
			 <li>Requests the remote resource
			 <li>Makes a log execute event
			 <li>Registers the RemoteResource's claimSock
			</ul>
			The parameters passed are all gotten from the
			command line and should be easy to figure out.
		*/
	void init( ClassAd* job_ad, const char* schedd_addr, const char *xfer_queue_contact_info );

	virtual void checkInputFileTransfer();

		/** Shadow should spawn a new starter for this job.
		 *  May be asynchronous if there's a shadow hook defined.
		 */
	void spawn( void );

		/**
		 * Callback after shadow hook has finished.
		 */
	void spawnFinish();

		/**
		 * Invoked when the job hook times out
		 */
	void hookTimeout( int timerID = -1 );
	void hookTimerCancel();

	const char * getStarterVersion() {
		if (remRes && !remRes->starter_version.empty()) { 
			return remRes->starter_version.c_str();
		}
		return nullptr;
	}

		/** Shadow should attempt to reconnect to a disconnected
			starter that might still be running for this job.
		 */
	void reconnect( void );

	bool supportsReconnect( void );

	/**
	 * override to allow starter+shadow to gracefully exit
	 */
	virtual void removeJob( const char* reason );

	/**
	 * override to allow starter+shadow to gracefully exit
	 */
	virtual void holdJob( const char* reason, int hold_reason_code, int hold_reason_subcode );

		/**
		 */
	int handleJobRemoval(int sig);

		/// Log an execute event to the UserLog
	void logExecuteEvent( void );

		/** Return the exit reason for this shadow's job.  Since we've
			only got 1 RemoteResource, we can just return its value.
		*/
	int getExitReason( void );

		/** Return true if the startd is not accepting more jobs on
			this claim.
		*/
	bool claimIsClosing( void );

		/* The number of bytes transferred from the perspective of
		 * the shadow (NOT the starter/job).
		 */
	uint64_t bytesSent();
	uint64_t bytesReceived();
	void getFileTransferStats(ClassAd &upload_stats, ClassAd &download_stats);
	void getFileTransferStatus(FileTransferStatus &upload_status,FileTransferStatus &download_status);

	int updateFromStarterClassAd(ClassAd* update_ad);

	struct rusage getRUsage( void );

	int64_t getImageSize( int64_t & mem_usage, int64_t & rss, int64_t & pss );

	int64_t getDiskUsage( void );

	bool exitedBySignal( void );

	int exitSignal( void );

	int exitCode( void );

		/** If desired, send the user email now that this job has
			terminated.  This has all the job statistics from the run,
			and lots of other useful info.
		*/
	virtual void emailTerminateEvent( int exitReason,
					update_style_t kind = US_NORMAL );

	// Record the file transfer state changes.
	virtual void recordFileTransferStateChanges( ClassAd * jobAd, ClassAd * ftAd );

		/** Do all work to cleanup before this shadow can exit.  We've
			only got 1 RemoteResource to kill the starter on.
		*/
	virtual void cleanUp( bool graceful=false );

		/** Do a graceful shutdown of the remote starter */
	virtual void gracefulShutDown( void );

	virtual void resourceBeganExecution( RemoteResource* rr );

	virtual void resourceDisconnected( RemoteResource* rr );

	virtual void resourceReconnected( RemoteResource* rr );

	virtual void logDisconnectedEvent( const char* reason );

	virtual bool getMachineName( std::string &machineName );

	/**
	 * Handle the situation where the job is to be suspended
	 */
	virtual int JobSuspend(int sig);

	/**
	 * Handle the situation where the job is to be continued.
	 */
	virtual int JobResume(int sig);

	virtual void exitAfterEvictingJob( int reason );
	virtual bool exitDelayed( int &reason );

	void exitLeaseHandler( int timerID = -1 ) const;

	ClassAd *getJobAd() { return remRes ? remRes->getJobAd() : nullptr; };

	virtual GuidanceResult pseudo_request_guidance( const ClassAd & request, ClassAd & guidance );

	virtual std::optional<ListOfCatalogs> computeCommonInputFileCatalogs(
		ClassAd * jobAd,
		std::map<std::string, std::string> * externalToSimpleNameMap
	);

	virtual bool computeCommonInputFiles(
		ClassAd * jobAd,
		ListOfCatalogs & commonFileCatalogs,
		int & required_version,
		std::map<std::string, std::string> * externalToSimpleNameMap
	);

	virtual std::optional<ClassAd> getCommonTransferInfoStats() {
		if( commonFTO == NULL ) { return {}; }
		return commonFTO->GetInfo().stats;
	}

 protected:

	virtual void logReconnectedEvent( void );

	virtual void logReconnectFailedEvent( const char* reason );

 private:

	RemoteResource *remRes;
	std::unique_ptr<ShadowHookMgr> m_hook_mgr;
	int m_exit_hook_timer_tid{-1};

	int delayedExitReason;

	void requestJobRemoval();

	// Flag to track if we should do a final file transfer before removing
	// Set when we receive TRANSFER_SANDBOX_AND_RM_JOB (condor_rm -transfer)
	bool transfer_and_remove_requested = false;

	//
	// Internal implementation details specific to pseudo_request_guidance().
	//

	ClassAd before_common_file_transfer(
		const std::string & cifName, const std::string & commonInputFiles
	);

	bool after_common_file_transfer(
		const ClassAd & request,
		const std::string & cifName,
		std::string & stagingDir
	);

	ClassAd handle_wiring_failure();

	condor::cr::Piperator<ClassAd, ClassAd> start_common_input_conversation(
		ClassAd request,
		ListOfCatalogs common_file_catalogs,
		bool print_waiting=true
	);


	// Use only with start_staging_only_conversation():
	// 		co_return VACATE_REQEUEST_ABORT(...);
	ClassAd vacate_requeue_abort(
		const std::string & holdMessage, CONDOR_HOLD_CODE holdCode, int holdSubCode,
		const char * file, int line
	);

	condor::cr::Piperator<ClassAd, ClassAd> start_staging_only_conversation(
		ClassAd request,
		ListOfCatalogs common_file_catalogs,
		std::map<std::string, std::string> externalToSimpleNameMap
	);

	condor::cr::Piperator<ClassAd, ClassAd> start_mapping_only_conversation(
		ClassAd request,
		ListOfCatalogs common_file_catalogs
	);



	void set_provider_keep_alive( const std::string & cifName );
	int producer_keep_alive = -1;

	// We only transfer one catalog at a time.
	FileTransfer * commonFTO = NULL;

	// The SingleProviderSyndicate can't be default-constructed.
	std::map< std::string, SingleProviderSyndicate * > cfLocks;
	// At some point we'll figure out nesting our coroutines and
	// we won't need this any more.
	bool resume_job_setup = false;
};

#endif
