/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

***************************************************************************/


#include <ddwebserver.h>
#include <ddwebpage.h>
#include <ddwebsite.h>
#include <sys/time.h>
#include <time.h>
#include <dditemlink.h>
#include <stdlib.h>

using namespace std;


/**
	Default constructor.
*/

ddwebsite::ddwebsite()
:	ddbasenamedobject( "localhost" ), _parent( 0 ), _connection( 0 )
{

}


/**
	Copy constructor
*/

ddwebsite::ddwebsite( const ddwebsite& src )
:	ddbasenamedobject("localhost"), _parent(0), _connection( 0 )
{
	*this = src;
}


/**
	Assignment operator.
*/

ddwebsite& ddwebsite::operator=( const ddwebsite& src )
{
  throw runtime_error("ddwebsite::operator=: not done!");
	if( this == &src )
		return *this;

	_parent = src._parent;
	_name = src._name;
	//_config = src._config;
	return *this;
}


/**
	Destructor.
*/

ddwebsite::~ddwebsite()
{
	
}



/**
	Eine Nachricht ans lokale domainlog schicken.
	@param	message the message
*/

void ddwebsite::log_message( int level, const ddstring& message )  const
{
	thelog.log_message( get_name(), level, message );
}


/**
	Eine Debug-Nachricht ans lokale domainlog schicken.
	@param	message the message
*/

bool ddwebsite::log_debug( const ddstring& message )  const
{
	log_message( ddlogdevice::lg_debug, message );
	return true;
}


/**
	Eine Error-Nachricht ans lokale domainlog schicken.
	@param	message the message
*/

bool ddwebsite::log_error( const ddstring& message ) const
{
	log_message( ddlogdevice::lg_error, message );
	return false;
}


/**
	Eine Error-Nachricht ans lokale domainlog schicken
	und mit einer entsprechenden exception abbrechen.
	
	@param	message the message
*/

void ddwebsite::log_fatal( const ddstring& message ) const
{
	log_message( ddlogdevice::lg_error, message );
	throw runtime_error( message );
}


/**
	Setzt alle Zugriffszähler zurück.
*/

void ddwebsite::reset_request_counter()
{
	for( int i=0; i<_pageletmap.size(); ++i )
		_pageletmap[i]->reset_request_counter();
	get_mail_manager().reset_reply_count();
}



/**
	Configure this domain object.
	@param name:
	@param path:
	@param configfile:
*/

void ddwebsite::init_site( ddwebserver* parent, const ddstring& basepath, ddstring& aliases )
{
	_parent = parent;
  _path = "sn_" + get_name();
  _path.to_dir_name();
	
	// init log manager first	
	
	thelog.add_log_domain( get_name(), get_working_dir() );
  
  ddstring filename = _path + get_name() + ".cfg";
	if( !_config.load( filename, _path + "../base.cfg" ) )
		throw runtime_error( "ddwebsite::init: load fail:" + filename );

	//_config.dump();

  aliases =  _config["main"].get_entry( "aliases" );
  
  // nicht nötig ??
  _docroot = _path + _config["main"].get_entry( "doc_root", "/" );
	_master_domain = _config["main"].get_entry( "master_domain", "" );
  
  // FIX! das ist der rest des style-handlings!
  // aufräumen!
	
	dditemset* itemset = new dditemset();
	itemset->init_items( basepath, _docroot ); // throws
	_itemsets.add( itemset );
	//// die basis templates laden

	// gilt für alle sites
	get_groups().init( get_config() );

	// FIX! unfug
  // setup db connection
 	_connection = new dddataconnection();
	_connection->connect( _config["main"]["connectstring"] );
		
	// setup virtual tables
	for( int i=0; i<_config.size(); ++i )
	{
    if( _config[i].get_name() == "translations" )
    {
      
			ddstring base = get_doc_root();
			base.to_dir_name();
						
			ddconfigsection& cfg = _config[i];
      for( int j=0; j < cfg.size(); ++j )
			{
				// Falluntescheidung: wort einfügen oder wort 
				// als filename interpretieren und den fileinhalt
				// einfügen.
				// FIX! hässlicher hack
				const ddstring& key = cfg.key_of( j );
					
				if( key.size() >= 3 && ( key.substr( 0,3 ) == "tr_" ) )
					_translations[key.substr(3)] = cfg[j];
				else
					_translations[key].from_file( base + cfg[j] );    											
			}
			continue;
	  }

		if( _config[i].get_type() != "table" )
			continue;

		const ddstring& key = _config[i].get_name();
  
  	// only add global tables, all other tables are private property of a pagelet.
    if( !key.contains( "site." ) )
			continue;
		
		_webviews.add_table( _config, _config[i] );
		
		
	}

	// setup messagemanager
	_formmanager.init_form_rules( *this );	
	
	// neu: die admin-sachen aus der config holen.
	// das "fill" hat alle user als member konstruiert, 
	// jetzt werden die admins befördert.
	
	int idx = _config.index_of( "admin" );
  if( idx<0 ) 
		throw runtime_error( "usermanager admin-config missing!" );		
	
	// für alle eingetragenen admins die rechte setzen.
	const ddconfigsection& section = _config[idx]; 
	for( int i=0; i<section.size(); ++i )
	{
		ddstring nickname = section.key_of( i );
	  dduserid* user;
	  
	  // statt nicknames können auch ids in der
	  // liste stehen.
	  //if( nickname.is_number() )
	  //	nickname = get_user_manager().get_nick_name( nickname );
	  user = &( get_user_manager()[ nickname.to_lower() ] );
		_sessionmanager.add_admin_access( user, section[i] );
	}
 	
 	// ipliste laden, aber nur, wenn sie noch leer ist, soll heissen:
 	// die ist global.
 	
 	ddstring content;
 	if( get_server().get_blocked_ips().empty() )
 	{
 		fetch_data_entry( DDKEYBLOCKEDIPS, content  );
		get_server().get_blocked_ips().from_string( content );
	}
	
	// mail adressen list laden, aber nur, wenn sie noch leer ist, soll heissen:
 	// die ist global.
 
	if( get_server().get_blocked_mails().empty() )
 	{
 		fetch_data_entry( DDKEYBLOCKEDMAILS, content  );
		get_server().get_blocked_mails().from_string( content );
	}
	
	// aliases laden
 	fetch_data_entry( DDKEYALIASESMAP, content );
	get_alias_map().from_string( content );
	
	// redirects laden
 	fetch_data_entry( DDKEYREDIRECTMAP, content );
	get_redirect_map().from_string( content );
	
	// linkersetzungsliste laden
 	fetch_data_entry( DDKEYLINKSET, content );
	get_overlay_links().from_string( content );
	size_t linkratio 				= _config["main"].get_int( "word_to_link_ratio", 5 );
	size_t linkreplacements	= _config["main"].get_int( "max_links_to_replace", 2 );
	size_t linkoccurence 		= _config["main"].get_int( "replace_link_occurence", 2 );
	get_overlay_links().init_properties( linkratio, linkreplacements, linkoccurence );

	// bildersetzungsliste laden
 	fetch_data_entry( DDKEYIMAGESET, content );
	get_overlay_images().from_string( content );
	
	// setup seealsomanager, throws... neue version
 	fetch_data_entry( DDKEYSTOPWORDS, content );
	// stopwords auch in die imageliste laden.
	get_overlay_images().init_stop_words( content, get_name() );
	
	get_related_manager().init( _config, content );	

	// setup mailmanager, throws...
	get_mail_manager().init_mailer( _config, get_working_dir() );
	
	// die tabellen der pagelets laden
	get_view_map().init_tables( get_config() );
	
	// categories laden
	ddwebview& categories = get_view( "site.categories" );
	categories.fill( "on_create_categories", get_db_name() );	
	get_category_map().init_data( categories );
	
	// webfilemanager setup
	get_file_manager().init_data( get_config() );
	// setup pagelets	
	init_pagelets(); // throws
	run_pagelets();
	
}



/**
	
*/

dditem* ddwebsite::lookup_item( const ddstring& key )
{
	/*
	Reste der Style-Verwaltung: FIX & CLEANUP
	// lookup item container
	if( styleid >= _itemsets.size() )
		throw out_of_range( "ddwebsite::lookup_item: no such item container!" );

	dditemset& itemset = _itemsets.get_entry( styleid );
	//itemset.get_index().dump();
	int i = itemset.index_of( key );
	if( i<0 )
		//throw runtime_error("ddwebsite::get_page: not found: " + key );
		return 0;
	return &(itemset[i]);
	*/

	dditemset& itemset = *_itemsets[0];
	int i = itemset.index_of( key );
	if( i<0 )
		//throw runtime_error("ddwebsite::get_page: not found: " + key );
		return 0;
	return &(itemset[i]);

}


/**
	Start page generator threads.
	@return true
*/

void ddwebsite::run_pagelets()
{
	for( int i=0; i<_pageletmap.size(); ++i )
    _pageletmap[i]->start_thread();
}


/**
	Find a pagelet.
*/

ddwebpage& ddwebsite::lookup_pagelet( const ddstring& name )
{
	ddthreadautomutex tmplock( _pageletlock );
	ddwebpage* pagelet = _pageletmap.lookup( name );
	if( !pagelet )
		throw out_of_range( "ddwebsite::get_pagelet: no such pagelet: " + name );
	return *pagelet;
}


/**
	Create the response: find and activate pagelet or send static file
*/

bool ddwebsite::website_handle_request( ddwebconnection& connection )
{
	
	ddwebrequest& request = connection.get_request();
  
  get_alias_map().prepare_request_redirect( request );
	
	ddstring trpath = localize_path( request );
	request.get_local_url().init_path( trpath );
	
	const ddstring& fullpath = connection.get_request().get_full_path();	
	const ddstring& rawpath  = connection.get_request().get_raw_path();	
	
	// neu: wenn der rawpath (mit cgi)) 'matcht', dann benutzen wir diesen
	// redirect, sonst testen mit dem pfad ohne cgi (fullpath)
	
	ddwebredirectmap& rmap = get_redirect_map();
	if( rmap.contains_key( rawpath ) )
	{		
		connection.get_response().get_location() = rmap[ rawpath ];
		connection.connection_handle_response( ddwebresponse::ret_301_moved_permanently, "text/html", false );
		return true;
	}
	
	if( rmap.contains_key( fullpath ) )
	{		
		connection.get_response().get_location() = rmap[ fullpath ];
		connection.connection_handle_response( ddwebresponse::ret_301_moved_permanently, "text/html", false );
		return true;
	}
	
	// create a dynamic page, if nessessary
	ddwebpage* pagelet = lookup_pagelet( request );
  // nix gefunden, es muss ein static file sein
  if( !pagelet )
  	return false;
	
	// init userid for the pagelet
	ddusermanager& userman = get_server().get_user_manager();
	dduserid* userid = userman.get_session_id( connection, get_session_manager() );		
	// store request		
	pagelet->enqueue_request( &connection, userid );
	// handle request
	pagelet->notify();		

	return true;
	
}


/**
	Macht den Pfad lokal, also aus html/bla.html /home/snuser/project/html/bla.html
*/

ddstring ddwebsite::localize_path( ddwebrequest& request )
{
	ddstring itempath = request.get_raw_path();	
  ddstring trpath = get_doc_root() + itempath;
  if( itempath.empty() )
		trpath.to_dir_name();

  if( itempath == "/" )
  {
		ddstring& defpath = _config["main"].get_entry( "index_page", "index.html" );
		trpath += defpath;
		request.get_split_path().add( defpath );
	}
  return trpath;

}


/**
	Gibt eine Referenz auf eine interne Tabelle zurück, 
	@see ddwebview
*/
ddwebview& ddwebsite::get_view( const ddstring& key )
{
   return get_view_map().get_entry( key );
}

/**
	rendert einen site-globalen block.
*/

dd_result ddwebsite::handle_block_render_call( ddwebpage& pagelet, dditem& item )
{
	// lock??
	
	// wir machen hier nicht die ganz große Wallung
	// (exception schmeissen)
	if( !_blockmap.contains_key( item.get_param() ) )
	{
		pagelet.write( "Error: Block nicht gefunden:" + item.get_name() + ": " + item.get_param() );
		return rs_ok;
	}
	return _blockmap.get_entry( item.get_param() ).handle_request( pagelet, item );
}


/**
	Bastelt den Nickname-Link
*/

void ddwebsite::create_user_link( dditem& entry, const ddstring& id, const ddstring& nickname )
{
	// create user link, if user is not guest
	if( id == "1" || id.empty() )
		entry.set_link( nickname, "" );
	else
		entry.set_link( nickname, DDMEMBERLINKURL + id, "member" );		
}


void ddwebsite::create_user_link( dditem& entry, const ddstring& id )
{
	ddwebsite::create_user_link( entry, id, get_user_manager().get_nick_name( id  ) );
}


/**
	Holt einen String aus der Blockdatenbank.
*/

void ddwebsite::fetch_data_entry( const ddstring& key, ddstring& target )
{
	ddthreadautomutex tmp( _dataentrylock );
	ddwebview& table = get_view( "site.blocklist" );
	table.fill( "select", get_db_name() );
	int idx = table.index_of( 0, key );
	if( idx < 0 )
		throw runtime_error( "ddwebsite::fetch_data_entry: key not found: " + key );
	target = table[idx][1];
	
}


/**
	Speichert einen String in der Blockdatenbank.
*/

void ddwebsite::store_data_entry( const ddstring& key, const ddstring& value )
{
	ddthreadautomutex tmp( _dataentrylock );
	ddwebview& table = get_view( "site.blocklist" );
	table.execute( "update", get_db_name(), value, key );	
}


/**
	Generiert einen neuen Passwort-String.
*/

ddstring ddwebsite::create_passwd()
{
	// create random crypt
  struct timeval tv;
  ::gettimeofday(&tv, 0);
  ::srand(tv.tv_usec);
  size_t crypt = (size_t) ::rand();
  return ddstrutil::from_hex( crypt, 8 ).to_upper();
}


/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

***************************************************************************/

/**
	Ab hier: snsite
*/

/**
	Den zuständigen Generator anhand des 
	Pfades finden. Pfuscheversion, die im Zweifel den .asp
	generator zurückgibt.
*/

ddwebpage* ddwebsite::lookup_pagelet( ddwebrequest& request)
{
	const ddstring& pagekey = request.get_split_path()[0];
	//if( !pagekey.empty)
	ddwebpage* pagelet = _pageletmap.lookup( pagekey );
	if( pagelet )
		return pagelet;
	return _pageletmap.lookup( "asp" );
}


ddstring& ddwebsite::get_user_content_count()
{
	ddthreadautomutex tmplock( _contentmutex );
	if( _contentcount.empty() )
	{
		ddstring result;
		get_view( "site.usercontent" ).execute( "contentcount" ).read( result );
    _contentcount = " (" + result + ")";
	}
	return _contentcount;
}



