package Siikir::Plugin::Bots 2012.0410;

use 5.14.0;
use strict;
use warnings;
use File::Copy;
use Siikir::Util;

use lib "./lib";
use RiveScript;

use base "Siikir::Plugin";

=head1 NAME

Siikir::Plugin::Bots - Chatterbot hosting service.

=cut

sub init {
	my $self = shift;

	$self->debug("Bots plugin loaded!");
	$self->requires("JsonDB");

	# Options.
	$self->options(
	);
}

=head1 FILES

Files that are required for the bot to work:

  cms/bots/bases/*
    Base brains for the bots. These are folder names like "eliza" and
    "blank".

=head1 METHODS

=head2 string create (hash options)

Create a new bot. Required options include:

  int owner: The user ID of the user who owns the bot.
  str name:  A name for the newly created bot.
  str base:  The base reply set for the bot.

Returns undef and sets C<$@> on error. Otherwise, returns a unique ID string
for the bot.

=cut

sub create {
	my ($self,%opts) = @_;

	my $id   = $opts{owner};
	my $name = Siikir::Util::stripHTML($opts{name});
	my $base = Siikir::Util::stripPaths($opts{base});

	# Validation.
	if (!$self->Master->User->userExists(id => $id)) {
		$@ = "Owner user ID $id not found.";
		return undef;
	}
	if (length $name == 0) {
		$@ = "The bot's name is required.";
		return undef;
	}
	if (length $name > 100) {
		$@ = "The bot's name is too long.";
		return undef;
	}
	if (!-d "$self->{root}/bots/bases/$base") {
		$@ = "(Internal error) That base brain was not found. Please contact the site admin.";
		return undef;
	}

	# Find an available bot ID number.
	my $bid = Siikir::Util::randomHash(8);
	while (-d "$self->{root}/bots/by-id/$bid") {
		$bid = Siikir::Util::randomHash(8);
	}

	# Initialize it.
	my $basedir = "$self->{root}/bots/by-id/$bid";
	mkdir($basedir);
	mkdir("$basedir/deparse"); # Deparsed JSON brains
	mkdir("$basedir/rs");      # Published RS documents

	# Base config file.
	$self->Master->JsonDB->writeJSON("$basedir/bot.json", {
		owner     => $id,
		botid     => $bid,
		name      => $name,
		base      => $base,
		created   => time(),
		published => undef, # Never published
	});

	# Copy templates over.
	opendir(my $dh, "$self->{root}/bots/templates");
	foreach my $json (sort(grep(/\.json$/, readdir($dh)))) {
		($json) = ($json =~ /^([A-Za-z0-9_]+.json)$/);
		copy("$self->{root}/bots/templates/$json", "$basedir/deparse/$json");
	}
	closedir($dh);

	# Create starter vars file.
	my $vars = {
		"begin" => {
			"var" => {
				name => $name,
			},
		},
	};
	$self->Master->JsonDB->writeJSON("$basedir/deparse/_vars.json", $vars);

	# Make a pointer to this bot in the main DB.
	$self->Master->JsonDB->writeDocument("bots/$id/$bid", {
		botid => $bid,
	});

	return $bid;
}

=head2 bool botExists (string botid)

Check if a bot ID exists.

=cut

sub botExists {
	my ($self,$bot) = @_;

	$bot = Siikir::Util::stripPaths($bot);
	return -d "$self->{root}/bots/by-id/$bot";
}

=head2 string[] listBots ([int userid])

Retrieve a list of bots (that belong to a userid, or not).

=cut

sub listBots {
	my ($self,$uid) = @_;
	$uid = Siikir::Util::stripID($uid);

	if (length $uid) {
		my @bots = $self->Master->JsonDB->listDocuments("bots/$uid");
		return @bots;
	}

	return undef; # TODO: list ALL THE BOTS
}

=head2 data getBotInfo (string botid)

Get the basic info about a bot (its name, base brain, owner ID, etc)

=cut

sub getBotInfo {
	my ($self,$bot) = @_;
	$bot = Siikir::Util::stripPaths($bot);

	if (!$self->botExists($bot)) {
		$@ = "The bot wasn't found.";
		return undef;
	}

	return $self->Master->JsonDB->readJSON("$self->{root}/bots/by-id/$bot/bot.json");
}

=head2 bool setBotInfo (string botid, data)

Write the bot's info.

=cut

sub setBotInfo {
	my ($self,$bot,$data) = @_;
	$bot = Siikir::Util::stripPaths($bot);

	if (!$self->botExists($bot)) {
		$@ = "The bot wasn't found.";
		return undef;
	}

	$self->Master->JsonDB->writeJSON("$self->{root}/bots/by-id/$bot/bot.json", $data);
	return 1;
}

=head2 data getDeparsed (string botid, filename)

Get a document from the bot's deparsed data. C<filename> would be e.g. "client",
not "client.json" or "client.rs".

=cut

sub getDeparsed {
	my ($self,$bot,$file) = @_;
	$bot = Siikir::Util::stripPaths($bot);
	$file = Siikir::Util::stripPaths($file);

	if (!$self->botExists($bot)) {
		$@ = "The bot wasn't found.";
		return undef;
	}

	my $path = "$self->{root}/bots/by-id/$bot/deparse/$file.json";
	if (-f $path) {
		return $self->Master->JsonDB->readJSON($path);
	}
	else {
		$@ = "Module not found.";
		return undef;
	}
}

=head2 void setDeparsed (string botid, string file, data deparsed)

Save deparsed data back to the JSON file.

=cut

sub setDeparsed {
	my ($self,$bot,$file,$data) = @_;
	$bot  = Siikir::Util::stripPaths($bot);
	$file = Siikir::Util::stripPaths($file);

	if (!$self->botExists($bot)) {
		$@ = "The bot wasn't found.";
		return undef;
	}

	my $path = "$self->{root}/bots/by-id/$bot/deparse/$file.json";
	$self->Master->JsonDB->writeJSON($path, $data);
	return 1;
}

=head2 void createDeparsed (string botid, string file)

Create a new deparse file. Doesn't allow overwriting an existing one.

=cut

sub createDeparsed {
	my ($self,$bot,$file) = @_;

	$bot  = Siikir::Util::stripPaths($bot);
	$file = Siikir::Util::stripPaths($file);

	if (!$self->botExists($bot)) {
		$@ = "The bot wasn't found.";
		return undef;
	}

	# File exists?
	if (-f "$self->{root}/bots/by-id/$bot/deparse/$file.json") {
		$@ = "Already exists.";
		return undef;
	}

	return $self->setDeparsed($bot, $file, {});
}

=head2 bool rmDeparsed (string botid, string file)

Delete a deparsed file.

=cut

sub rmDeparsed {
	my ($self,$bot,$file) = @_;
	$bot  = Siikir::Util::stripPaths($bot);
	$file = Siikir::Util::stripPaths($file);

	if (!$self->botExists($bot)) {
		$@ = "The bot wasn't found.";
		return undef;
	}

	my $path = "$self->{root}/bots/by-id/$bot/deparse/$file.json";
	if (-f $path) {
		unlink($path);
	}

	return 1;
}

=head2 bool publish (string botid)

Publish the bot. Converts all the deparsed files into RiveScript
documents.

=cut

sub publish {
	my ($self,$bot) = @_;
	$bot = Siikir::Util::stripPaths($bot);

	if (!$self->botExists($bot)) {
		$@ = "The bot wasn't found.";
		return undef;
	}

	my $base = "$self->{root}/bots/by-id/$bot";

	# Delete all existing RS files.
	opendir (my $rsd, "$base/rs");
	foreach my $rs (sort(grep(/\.rs$/i, readdir($rsd)))) {
		($rs) = ($rs =~ /^(.+?\.rs)$/i);
		unlink("$rsd/$rs");
	}
	closedir($rsd);

	# Load all the deparsed files.
	opendir (my $dp, "$base/deparse");
	foreach my $json (sort(grep(/\.json$/i, readdir($dp)))) {
		my ($name) = ($json =~ /^(.+?)\.json$/i);
		my $data = $self->getDeparsed($bot, $name);

		# Translate to RiveScript.
		my $rs = RiveScript->new();
		$rs->write("$base/rs/$name.rs", $data);
	}
	closedir($dp);

	# Update last published time.
	my $db = $self->getBotInfo($bot);
	$db->{published} = time();
	$self->setBotInfo($bot, $db);

	return 1;
}

=head2 string[] listDeparsed (string botid)

Retrieve a list of all deparsed documents.

=cut

sub listDeparsed {
	my ($self,$bot) = @_;
	$bot = Siikir::Util::stripPaths($bot);

	if (!$self->botExists($bot)) {
		$@ = "The bot wasn't found.";
		return undef;
	}

	my $base = "$self->{root}/bots/by-id/$bot";

	# List deparsed files.
	my @files = ();
	opendir(my $dh, "$base/deparse");
	foreach my $json (sort(grep(/\.json$/i, readdir($dh)))) {
		my ($name) = ($json =~ /^(.+?)\.json$/);
		push @files, $name;
	}
	closedir($dh);

	return @files;
}

1;
