package App::Office::CMS::Controller;

use parent 'CGI::Application';
use strict;
use warnings;

use App::Office::CMS::Util::Config;
use App::Office::CMS::Util::Validator;
use App::Office::CMS::Database;
use App::Office::CMS::View;

use Data::Session;

use JSON::XS;

use Text::Xslate;

# We don't use Moo because we ias CGI::Application.

our $VERSION = '0.93';

# -----------------------------------------------

sub build_page_hash
{
	my($self, $valid) = @_;

	$self -> log(debug => 'build_page_hash()');

	my($page) = {};

	my($field_name);

	# Real data.

	for $field_name (qw/asset_type_id homepage menu_orientation_id name new_name/)
	{
		$$page{$field_name} = $$valid{$field_name};
	}

	# Submit buttons.

	for $field_name (qw/submit_add_menu submit_delete_page submit_extend_menu_left submit_extend_menu_right submit_extend_submenu_down submit_extend_submenu_up submit_update_page/)
	{
		$$page{$field_name} = $$valid{$field_name};
	}

	return $page;

} # End of build_page_hash.

# -----------------------------------------------

sub build_site_hash
{
	my($self, $valid) = @_;

	$self -> log(debug => 'build_site_hash()');

	my($site) =
	{
		menu_orientation_id => 4, # Vertical.
		os_type_id          => 3, # Unix.
	};

	for my $field_name (qw/design_name name new_name output_directory output_doc_root/)
	{
		$$site{$field_name} = $$valid{$field_name};
	}

	return $site;

} # End of build_site_hash.

# -----------------------------------------------

sub cgiapp_prerun
{
	my($self, $rm) = @_;

	# Can't call, since db's logger not yet set up.
	#$self -> log(debug => 'cgiapp_prerun()');

	$self -> param(config => App::Office::CMS::Util::Config -> new -> config);
	$self -> param(db     => App::Office::CMS::Database -> new
	(
	 config => $self -> param('config'),
	) );

	# Set up the session. To simplify things we always use
	# CGI::Session, and ignore the PSGI alternative.

	my($config) = $self -> param('config');
	my($q)      = $self -> query;

	$self -> param(session =>
	Data::Session -> new
	(
		data_source => $$config{dsn},
		dbh         => $self -> param('db') -> dbh,
		name        => 'sid',
		pg_bytea    => $$config{pg_bytea} || 0,
		pg_text     => $$config{pg_text}  || 0,
		query       => $q,
		table_name  => $$config{session_table_name},
		type        => $$config{session_driver},
	) );

	# Set up a few more things.

	$self -> param('db') -> set_session($self -> param('session') );
	$self -> param
	(
	 templater =>
	   Text::Xslate -> new
	   (
		input_layer => '',
		path        => ${$self -> param('config')}{template_path},
	   )
	);

	# Other controllers add their own run modes.

	$self -> run_modes([qw/display/]);
	$self -> tmpl_path(${$self -> param('config')}{template_path});

	# Log the CGI form parameters.

	$self -> log(info  => '');
	$self -> log(info  => $q -> url(-full => 1, -path => 1) );
	$self -> log(info  => "Param: $_: " . $q -> param($_) ) for grep{! /^(?:body_text|head_text)$/} $q -> param;
	$self -> log(info  => 'Session id: ' . $self -> param('session') -> id);
	$self -> log(debug => 'tmpl_path: ' . $self -> tmpl_path);

	# Set up the view.

	$self -> param(view => App::Office::CMS::View -> new
	(
	 config      => $self -> param('config'),
	 db          => $self -> param('db'),
	 form_action => $self -> query -> url(-absolute => 1),
	 session     => $self -> param('session'),
	 templater   => $self -> param('templater'),
	) );

} # End of cgiapp_prerun.

# -----------------------------------------------

sub check_page_name
{
	my($self, $page, $action) = @_;
	$action ||= '';

	$self -> log(debug => "check_page_name($$page{name}, $action)");

	my($design_id)  = $self -> param('session') -> param('edit_design_id');
	my($site_id)    = $self -> param('session') -> param('edit_site_id');
	my($page_match) = $self -> param('db') -> page -> get_page_by_exact_name($site_id, $design_id, $$page{name});

	my($asset);
	my($message);

	if ($page_match)
	{
		# Firstly, prepare new name just in case we need it.

		$$page_match{new_name} = $$page{new_name};

		# Update user's choices.
		# Values for $action, and their meanings when page name matches:
		# o add    => It's not an add, it's an update
		# o click  => The user is changing pages via the Javascript menu
		# o delete => It's a delete
		# o edit   => The user is editing content
		# o update => It's an update

		if ($action =~ /(?:add|update)/)
		{
			$asset                 = $self -> param('db') -> asset -> get_asset_by_page_id($$page_match{id});
			$$asset{asset_type_id} = $$page{asset_type_id};
			$$page_match{homepage} = $$page{homepage};
		}
	}
	elsif ($action =~ /(?:add|update)/)
	{
		my($design_id) = $self -> param('session') -> param('edit_design_id');
		my($design)    = $self -> param('db') -> design -> get_design_by_id($design_id);
		my($site_id)   = $self -> param('session') -> param('edit_site_id');
		my($site)      = $self -> param('db') -> site -> get_site_by_id($site_id);
		$page_match    = $self -> param('db') -> build_default_page($site, $design, $$page{name});
		$asset         = $self -> param('db') -> build_default_asset($page_match);

		# Update user's choices.

		$$asset{asset_type_id} = $$page{asset_type_id};
		$$page_match{homepage} = $$page{homepage};
	}
	else
	{
		$message = "Error: No page matches the name '$$page{name}'";
	}

	# Note: By returning $page_match, we lose the CGI form fields.

	return ($message, $page_match, $asset);

} # End of check_page_name.

# -----------------------------------------------

sub check_site_and_design_names
{
	my($self, $site, $action) = @_;
	$action ||= '';

	$self -> log(debug => "check_site_and_design_names($$site{name}, $action)");

	# Did the client submit an existing site?

	my($site_match) = $self -> param('db') -> site -> get_site_by_exact_name($$site{name});

	my($design);
	my($message);

	if ($site_match)
	{
		# Firstly, prepare new name just in case the client clicked 'Duplicate site'.

		$$site_match{new_name} = $$site{new_name};

		# Secondly, did the client submit an existing design?

		$design = $self -> param('db') -> design -> get_design_by_exact_name($$site_match{id}, $$site{design_name});

		# Thirdly, prepare new name just in case the client clicked 'Duplicate design'.
		# But, we don't need to put the new name in the design object because
		# Controller::Design.duplicate() passes $site_match to Database::Design.duplicate().
		# Note: The client must specify an existing design for 'Duplicate design' to work.

		if ($design)
		{
			# Update user's choices.

			#$$design{menu_orientation_id} = $$site{menu_orientation_id};
			#$$design{os_type_id}          = $$site{os_type_id};
			$$design{output_directory}    = $$site{output_directory};
			$$design{output_doc_root}     = $$site{output_doc_root};
		}
		elsif ($action =~ /(?:add|duplicate_site|update)/)
		{
			# When duplicating a site, the default design manufactured here is ignored.

			$design = $self -> param('db') -> build_default_design($$site_match{id}, $$site{design_name}, $$site{menu_orientation_id}, $$site{os_type_id}, $$site{output_directory}, $$site{output_doc_root});
		}
		else
		{
			$message = "Error: No design matches the name '$$site{design_name}' for site '$$site{name}'";
		}
	}
	elsif ($action =~ /(?:add|update)/)
	{
		$site_match = $self -> param('db') -> build_default_site($$site{name});
		$design     = $self -> param('db') -> build_default_design(0, $$site{design_name}, $$site{menu_orientation_id}, $$site{os_type_id}, $$site{output_directory}, $$site{output_doc_root});
	}
	else
	{
		$message = "Error: No site matches the name '$$site{name}'";
	}

	# Note: By returning $site_match, we loose the CGI form fields.

	return ($message, $site_match, $design);

} # End of check_site_and_design_names.

# -----------------------------------------------

sub log
{
	my($self, $level, $s) = @_;

	$self -> param('db') -> log($level, $s);

} # End of log.

# -----------------------------------------------

sub process_page_form
{
	my($self, $action) = @_;

	$self -> log(debug => "process_page_form($action)");

	my($data) = App::Office::CMS::Util::Validator -> new
	(
	 config => $self -> param('config'),
	 db     => $self -> param('db'),
	 query  => $self -> query,
	) -> validate_page;

	my($asset);
	my($message);
	my($page);

	if ($$data{_rejects})
	{
		$self -> log(debug => 'Page data is not valid');

		$message => $self -> param('view') -> format_errors($$data{_rejects});
	}
	else
	{
		$self -> log(debug => 'Page data is valid');

		$page                     = $self -> build_page_hash($data);
		($message, $page, $asset) = $self -> check_page_name($page, $action);
	}

	return ($message, $page, $asset);

} # End of process_page_form.

# -----------------------------------------------

sub process_site_and_design_form
{
	my($self, $action) = @_;

	$self -> log(debug => "process_site_and_design_form($action)");

	my($data) = App::Office::CMS::Util::Validator -> new
	(
	 config => $self -> param('config'),
	 db     => $self -> param('db'),
	 query  => $self -> query,
	) -> validate_site_and_design;

	my($design);
	my($message);
	my($site);

	if ($$data{_rejects})
	{
		$self -> log(debug => 'Site and design data is not valid');

		$message = $self -> param('view') -> format_errors($$data{_rejects});
	}
	else
	{
		$self -> log(debug => 'Site and design data is valid');

		$site                      = $self -> build_site_hash($data);
		($message, $site, $design) = $self -> check_site_and_design_names($site, $action);
	}

	return ($message, $site, $design);

} # End of process_site_and_design_form.

# -----------------------------------------------

sub teardown
{
	my($self) = @_;

	$self -> log(debug => 'teardown()');

	# This is mandatory under Plack.

	$self -> param('session') -> flush;

} # End of teardown.

# -----------------------------------------------

1;
