// $Id: methods.src 1238 2008-08-16 06:37:29Z MadmanAtW $

/*
From core-changes.txt:

Added support for the client's book interface:
uo::SendOpenBook(chr,book) kicks it off

The 'book' passed must have a Method Script, exporting the following functions:
	exported function IsWritable(book) - returns 0 or 1 for readonly/writable
	exported function GetNumLines(book) - returns maximum number of lines
        exported function GetTitle(book)
        exported function GetAuthor(book)
        exported function GetLine(book, line) - line is 1-based

Writable books must provide the following functions:
        exported function SetAuthor(book,author)
        exported function SetTitle(book,title)
        exported function SetLine(book,line,text)
        exported function GetContents(book) - returns an array of all lines
*/

/////////////////////////////////////////////////////////////////
// * Function List *
//
// IsBook(book)
//
// IsWritable(book)
// GetNumLines(book)
// GetTitle(book)
// GetAuthor(book)
// GetLine(book, line_num)
// GetContents(book)
// SetAuthor(book, author)
// SetTitle(book, title)
// SetLine(book, line_num, text)
// SetOwner(book, serial)
// GetOwner(book)
// ReadOnly(book, value)
// ExtractInfo(book)
//
/////////////////////////////////////////////////////////////////
// * Internal/Private Function List *
//
// GetInfo(book, prop_name)
// SetInfo(book, prop_name, prop_value)
//
///////////////////////////

use uo;
use os;
use cfgfile;

include ":datafile:datafile";

program Install()
	return 1;
endprogram

exported function IsBook(book)
	return book;
endfunction

//++++++++++++++++++++++++++++
// Core Required Functions
//++++++++++++++++++++++++++++

exported function IsWritable(book)
	if ( GetInfo(book, "ReadOnly") )
		return 0;
	else
		return 1;
	endif
endfunction

exported function GetNumLines(book)
	// There are 8 lines per page.
	// For simplicity, take in the number of pages and return 8 lines for every page.
	return (CInt(GetInfo(book, "NumPages")) * 8);
endfunction

exported function GetTitle(book)
	//Looks for a property called "Title".
	//If not set, converts the false to "".
	var title := GetInfo(book, "Title");
	
	if (!title)
		title := "";
	endif
	
	return CStr(title);
endfunction

exported function GetAuthor(book)
	//:ooks for a property called "Author".
	//If not set, converts the false to "".
	var author := GetInfo(book, "Author");
	
	if (!author)
		author := "";
	endif
	
	return CStr(author);
endfunction

exported function GetLine(book, line_num)
	// Used to get a specific line from the book.

	// Uses the GetContents() function.
	var contents := GetContents(book);
	if ( line_num <= contents.Size() )
		var line := contents[line_num];

		if ( line && line != error )
			return line;
		endif
	endif

	// If there wasnt a line, return an empty string.
	return "";
endfunction

exported function GetContents(book)
	// Returns an array of all lines
	var info := GetInfo(book, "Contents");
	if ( !info || info == error )
		info := array;
	endif
	return info;
endfunction

exported function SetAuthor(book, author)
	return SetInfo(book, "Author", CStr(author));
endfunction

exported function SetTitle(book, title)
	if ( Len(title) > 0 )
		SetName(book, title);
	else
		SetName(book, "a book");
	endif
	
	return SetInfo(book, "Title", CStr(title));
endfunction

exported function SetLine(book, line_num, text)
	// Gets all the lines, then edits the appropriate line.
	// Returns the contents of the book.

	// Reuses the GetContents() function.
	var contents := GetContents(book);
	if ( !contents || contents == error )
		contents := array;
	endif

	//Reuses the GetNumLines() function
	if ( line_num <= GetNumLines(book) )
		contents[line_num] := CStr(text);
		SetInfo(book, "Contents", contents);
	endif

	return contents;
endfunction

//++++++++++++++++++++++++++++
// Helper Functions
//++++++++++++++++++++++++++++

exported function SetOwner(book, serial)
	if ( serial.IsA(POLCLASS_MOBILE) )
		serial := serial.serial;
	endif
	
	//book.SetOwner(serial)
	//Sets the serial of who owns the book.
	return SetInfo(book, "Owner", CInt(serial));
endfunction

exported function GetOwner(book)
	//book.GetOwner()
	//Gets the serial of the owner of the book.
	return GetInfo(book, "Owner");
endfunction

exported function ReadOnly(book, mode)
	// book.ReadOnly(1|0)
	// Mode:
	//	1 - Disable read write (sets to read only)
	//	0 - Enable read write

	mode := CInt(mode);
	if ( mode < 0 )
		mode := 0;
	elseif ( mode > 1 )
		mode := 1;
	endif

	return SetInfo(book, "ReadOnly", CInt(mode));
endfunction

exported function ExtractInfo(book)
	// book.ExtractInfo()
	// Get all the info you need about a book
	// useful for copying one or something else.

	// Returns a structure
	// .Author
	// .Contents
	// .Title

	var book_info := struct;
	book_info.+Author := GetAuthor(book);
	book_info.+Contents := GetContents(book);
	book_info.+Title := GetTitle(book);

	return book_info;
endfunction

exported function GetNumPages(book)
	return CInt(GetInfo(book, "NumPages"));
endfunction

exported function SetNumPages(book, num_pages)
	return SetInfo(book, "NumPages", CInt(num_pages));
endfunction

//++++++++++++++++++++++++++++
// Internal/Private Functions
//++++++++++++++++++++++++++++

// *GetInfo(book, prop_name)
//
// Function for getting a book's info.
// First it will search for a datafile entry for the value based on
// the book's serial. Then it will look on the book for a cprop, then
// if neither of the previous two were found, it will fall back on the
// itemdesc.cfg entry for the book.

function GetInfo(book, prop_name)
	var prop_value;

	// First we check for a datafile entry.
	// We do create the datafile if it doesnt exist.
	// However, the elem is not created if it doesnt exist
	// because were only reading in this function and not writing.
	var book_data_file := DFOpenDataFile(":sysbook:books", DF_CREATE);
	var book_elem := DFFindElement(book_data_file, Hex(book.serial), DF_NO_CREATE);
	prop_value := book_elem.GetProp(prop_name);
	if ( prop_value )
		return prop_value;
	endif

	//If we got here, then there was no datafile entry for
	//the property passed. So at this point we check the book
	//for a cprop.
	prop_value := GetObjProperty(book, prop_name);
	if ( prop_value )
		return prop_value;
	endif

	//If we got this far.. then lets fall back on the itemdesc.cfg
	//Note its read in as :*: which reads in every itemdesc.cfg
	//See 0x7021 itemdesc entry for example
	var cfg := ReadConfigFile(":*:itemdesc");
	cfg := cfg[book.objtype];
	if ( prop_name == "Contents" )
		prop_value := GetConfigStringArray(cfg, prop_name);
	else
		prop_value := GetConfigString(cfg, prop_name);
	endif
	if ( prop_value )
		return prop_value;
	endif

	return 0;
endfunction

// *SetInfo(book, prop_name, prop_value)
//
// Private function for setting a value for a book.
// Writes to a datafile, the elem being the book's serial #
//
// Note: Make sure the prop_name never has any spaces.
//       Datafiles dont like that.
//
function SetInfo(book, prop_name, prop_value)
	var book_data_file := DFOpenDataFile(":sysbook:books", DF_CREATE);
	var book_elem := DFFindElement(book_data_file, Hex(book.serial), DF_CREATE);

	return book_elem.SetProp(CStr(prop_name), prop_value);
endfunction
