//----------------------------------------------------------------------
//  This software is part of the OpenBeOS distribution and is covered 
//  by the OpenBeOS license.
//
//  Copyright (c) 2003 Tyler Dauwalder, tyler@dauwalder.net
//----------------------------------------------------------------------

/*! \file UdfBuilder.h

	Main UDF image building class interface declarations.
*/

#ifndef _UDF_BUILDER_H
#define _UDF_BUILDER_H

#include <Entry.h>
#include <list>
#include <Node.h>
#include <stdarg.h>
#include <string>
#include <SupportDefs.h>

#include "Allocator.h"
#include "FileStream.h"
#include "MemoryStream.h"
#include "PhysicalPartitionAllocator.h"
#include "ProgressListener.h"
#include "Statistics.h"
#include "UdfString.h"

/*! \brief Handy struct into which all the interesting information about
	a processed directory, file, or whatever is placed by the corresponding
	UdfBuilder::_Process*() function.
*/
struct node_data {
	Udf::long_address icbAddress;			//!< Udf icb address
	std::list<Udf::long_address> udfData;	//!< Dataspace for node in Udf partition space
	std::list<Udf::extent_address> isoData;	//!< Dataspace for node in physical space
};

class UdfBuilder {
public:
	UdfBuilder(const char *outputFile, uint32 blockSize, bool doUdf,
	           const char *udfVolumeName, uint16 udfRevision, bool doIso,
	           const char *isoVolumeName,
	           const char *rootDirectory, const ProgressListener &listener);
	status_t InitCheck() const;
	status_t Build();
private:
	//! Maximum length of string generated by calls to any _Print*() functions
	static const int kMaxUpdateStringLength = 1024;

	FileStream& _OutputFile() { return fOutputFile; }
	uint32 _BlockSize() const { return fBlockSize; }
	uint32 _BlockShift() const { return fBlockShift; }
	bool _DoUdf() const { return fDoUdf; }
	Udf::String& _UdfVolumeName() { return fUdfVolumeName; }
	uint16 _UdfRevision() const { return fUdfRevision; }
	uint16 _UdfDescriptorVersion() const { return fUdfDescriptorVersion; }
	const Udf::entity_id& _UdfDomainId() const { return fUdfDomainId; }
	bool _DoIso() const { return fDoIso; }
	Udf::String& _IsoVolumeName() { return fIsoVolumeName; }
	BEntry& _RootDirectory() { return fRootDirectory; }
	Allocator& _Allocator() { return fAllocator; }
	PhysicalPartitionAllocator& _PartitionAllocator() { return fPartitionAllocator; }
	Statistics& _Stats() { return fStatistics; }
	time_t _BuildTime() const { return fBuildTime; }
	Udf::timestamp& _BuildTimeStamp() { return fBuildTimeStamp; }
	uint64 _NextUniqueId();
	bool _32BitIdsNoLongerUnique() const { return f32BitIdsNoLongerUnique; }

	void _SetBuildTime(time_t time);

	status_t _FormatString(char *message, const char *formatString, va_list arguments) const;
	void _PrintError(const char *formatString, ...) const;
	void _PrintWarning(const char *formatString, ...) const;
	void _PrintUpdate(VerbosityLevel level, const char *formatString, ...) const;
	
	status_t _ProcessDirectory(BEntry &entry, const char *path, struct stat stats,
	                           node_data &node, Udf::long_address parentIcbAddress,
	                           bool isRootDirectory = false);
	status_t _ProcessFile(BEntry &entry, const char *path, struct stat stats,
	                      node_data &node);
	status_t _ProcessSymlink(BEntry &symlink);
	status_t _ProcessAttributes(BNode &node);
	
	template <class FileEntry>
		status_t _WriteFileEntry(FileEntry *icb, uint8 fileType, uint16 linkCount,
		                         uint64 dataLength, uint64 objectSize, struct stat stats,
		                         uint64 uniqueId, uint32 allocationDescriptorsLength,
		                         Udf::tag_id fileEntryType, Udf::long_address icbAddress,
		                         Udf::extent_address icbExtent,
		                         std::list<Udf::long_address> dataAddresses);

	status_t fInitStatus;
	FileStream fOutputFile;
	std::string fOutputFilename;
	uint32 fBlockSize;
	uint32 fBlockShift;
	bool fDoUdf;
	Udf::String fUdfVolumeName;
	uint16 fUdfRevision;
	uint16 fUdfDescriptorVersion;
	const Udf::entity_id &fUdfDomainId;
	bool fDoIso;
	Udf::String fIsoVolumeName;
	BEntry fRootDirectory;
	std::string fRootDirectoryName;
	const ProgressListener &fListener;
	Allocator fAllocator;
	PhysicalPartitionAllocator fPartitionAllocator;
	Statistics fStatistics;
	time_t fBuildTime;
	Udf::timestamp fBuildTimeStamp;
	uint64 fNextUniqueId;
	bool f32BitIdsNoLongerUnique;
};

template <class FileEntry>
status_t
UdfBuilder::_WriteFileEntry(FileEntry *icb, uint8 fileType, uint16 linkCount,
		                    uint64 dataLength, uint64 objectSize, struct stat stats,
		                    uint64 uniqueId, uint32 allocationDescriptorsLength,
		                    Udf::tag_id fileEntryType, Udf::long_address icbAddress,
		                    Udf::extent_address icbExtent,
		                    std::list<Udf::long_address> dataAddresses)
{
	DEBUG_INIT_ETC("UdfBuilder", ("type: %s", icb->descriptor_name()));
	status_t error = B_ERROR;
	Udf::icb_entry_tag &itag = icb->icb_tag();
	itag.set_prior_recorded_number_of_direct_entries(0);
	itag.set_strategy_type(Udf::ICB_STRATEGY_SINGLE);
	memset(itag.strategy_parameters().data, 0,
	       itag.strategy_parameters().size());
	itag.set_entry_count(1);
	itag.reserved() = 0;
	itag.set_file_type(fileType);
	itag.parent_icb_location() = kNullLogicalBlock;
	Udf::icb_entry_tag::flags_accessor &iflags = itag.flags_access();
	// clear flags, then set those of interest
	iflags.all_flags = 0;	
	iflags.flags.descriptor_flags = Udf::ICB_DESCRIPTOR_TYPE_LONG;
	iflags.flags.archive = 1;
	icb->set_uid(0xffffffff);
	icb->set_gid(0xffffffff);
	icb->set_permissions(Udf::OTHER_EXECUTE | Udf::OTHER_READ
	                    | Udf::GROUP_EXECUTE | Udf::GROUP_READ
	                    | Udf::USER_EXECUTE | Udf::USER_READ);
	icb->set_file_link_count(linkCount);
	icb->set_record_format(0);
	icb->set_record_display_attributes(0);
	icb->set_record_length(0);
	icb->set_information_length(dataLength);
	icb->set_object_size(objectSize);	// EFE only
	icb->set_logical_blocks_recorded(_Allocator().BlocksFor(dataLength));
	icb->access_date_and_time() = Udf::timestamp(stats.st_atime);
	icb->modification_date_and_time() = Udf::timestamp(stats.st_mtime);
	icb->creation_date_and_time() = Udf::timestamp(stats.st_crtime);	// EFE only
	icb->attribute_date_and_time() = icb->creation_date_and_time();
	icb->set_checkpoint(1);
	icb->set_reserved(0);	// EFE only
	icb->extended_attribute_icb() = kNullAddress;
	icb->stream_directory_icb() = kNullAddress;	// EFE only
	icb->implementation_id() = Udf::kImplementationId;
	icb->set_unique_id(uniqueId);
	icb->set_extended_attributes_length(0);
	icb->set_allocation_descriptors_length(allocationDescriptorsLength);				
	icb->tag().set_id(fileEntryType);
	icb->tag().set_version(_UdfDescriptorVersion());
	icb->tag().set_serial_number(0);
	icb->tag().set_location(icbAddress.block());

	// write allocation descriptors
	std::list<Udf::long_address>::iterator a;
	MemoryStream descriptorStream(icb->allocation_descriptors(),
	                              allocationDescriptorsLength);
	error = descriptorStream.InitCheck();
	if (!error) {                              
		for (a = dataAddresses.begin();
		       a != dataAddresses.end() && error == B_OK;
		         a++)
		{
			PRINT(("Dumping address:\n"));
			DUMP(*a);
			Udf::long_address &address = *a;
			ssize_t bytes = descriptorStream.Write(&address, sizeof(address));
			error = check_size_error(bytes, sizeof(address));			                             
		}
	}
	icb->tag().set_checksums(*icb, icb->descriptor_size());
	PDUMP(icb);
			
	// Write udf icb
	if (!error) {
		_PrintUpdate(VERBOSITY_MEDIUM, "udf: Writing icb");
		// write icb
		_OutputFile().Seek(icbExtent.location() << _BlockShift(), SEEK_SET);
		PRINT(("position, icbsize: %Ld, %ld\n", _OutputFile().Position(), sizeof(icb)));
		ssize_t bytes = _OutputFile().Write(icb, _BlockSize());
		PRINT(("position: %Ld\n", _OutputFile().Position()));
		error = check_size_error(bytes, _BlockSize());
		PRINT(("position: %Ld\n", _OutputFile().Position()));
	}
	RETURN(error);
}

#endif	// _UDF_BUILDER_H
