/* @(#)vote.c 1.5 95/09/16 */

/*
 * Copyright (c) 1994, 1995 by Wayne C. Gramlich.  All rights reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * for any purpose is hereby granted without fee provided that the above
 * copyright notice and this permission are retained.  The author makes
 * no representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 */

/* LINTLIBRARY */

#include <stdio.h>

#include "assert.h"
#include "config_extern.h"
#include "error_extern.h"
#include "link_extern.h"
#include "memory_extern.h"
#include "remote_extern.h"
#include "str_extern.h"
#include "url_extern.h"
#include "util_extern.h"
#include "vote_extern.h"
#include "vector_extern.h"

struct vote_struct {
    Str comment;
    Str email;
    Link link;
    unsigned yes;
};

static Vote vote_create(Link);
static void vote_save(Vote, FILE *);
static Vote vote_restore(Link, FILE *, Errors);

static void votes_append(Votes, Vote);
static void votes_delete(Votes, unsigned);

/*
 * vote_create(link)
 *	This routine will create and return a new {Vote} object for {link}.
 */
static Vote
vote_create(
    Link link)
{
    Vote vote;

    vote = (Vote)memory_allocate_zeroed(sizeof *vote);
    vote->link = link;
    return vote;
}

/*
 * vote_save(vote, out_file)
 *	This routine will write {vote} to {out_file}.
 */
static void
vote_save(
    Vote vote,
    FILE *out_file)
{
    (void)fprintf(out_file, "V %u ", vote->yes);
    str_save(vote->email, out_file);
    (void)fprintf(out_file, " ");
    str_save(vote->comment, out_file);
    (void)fprintf(out_file, "\n");
}

/*
 * vote_restore(link, in_file, errors)
 *	This routine will read the next vote record from {in_file} and
 *	return the resulting {Vote} object for {link}.  Any errors
 *	are reported using {errors}.
 */
static Vote
vote_restore(
    Link link,
    FILE *in_file,
    Errors errors)
{
    Vote vote;

    vote = vote_create(link);
    (void)fscanf(in_file, "V %u ", &vote->yes);
    vote->email = str_restore(in_file);
    if (getc(in_file) != ' ') {
	goto error;
    }
    vote->comment = str_restore(in_file);
    if (getc(in_file) != '\n') {
	goto error;
    }
    return vote;
  error:
    errors_append(errors, (Str)"vote restore problem.");
    return vote;
}

/*
 * vote_yes(vote)
 *	This routine will return the value of the yes field in {vote}.
 */
int
vote_yes(
    Vote vote)
{
    return vote->yes;
}

/*
 * votes_delete(votes, index)
 *	This routine will delete the {index}'th vote from {votes}.
 */
static void
votes_delete(
    Votes votes,
    unsigned index)
{
    vector_delete((Vector)votes, index);
}

/*
 * votes_save(votes, out_file)
 *	This routine will write {votes} to {out_file}.
 */
void
votes_save(
    Votes votes,
    FILE *out_file)
{
    Vote vote;

    (void)fprintf(out_file, "%u\n", votes_size(votes));
    VOTES_LOOP(vote, votes) {
	vote_save(vote, out_file);
    }
}

/*
 * votes_save(votes, out_file)
 *	This routine will write {votes} to {out_file}.
 */
Votes
votes_restore(
    Link link,
    FILE *in_file,
    Errors errors)
{
    unsigned index;
    unsigned size;
    Vote vote;
    Votes votes;

    (void)fscanf(in_file, "%u\n", &size);
    votes = votes_create();
    for (index = 0; index < size; index++) {
	vote = vote_restore(link, in_file, errors);
	if (errors_size(errors) != 0) {
	    errors_append(errors, str_printf("Restoring vote %u", index));
	}
	votes_append(votes, vote);
    }
    return votes;
}

/*
 * vote_write_html(vote, out_file)
 *	This routine will write out the vote associated {vote} to {out_file}.
 *	The HTML that is output is assumed to be embedded in a <DL>...</DL>
 *	entity.
 */
void
vote_write_html(
    Vote vote,
    FILE *out_file)
{
    Str vote_str;

    vote_str = vote->yes ? (Str)"Yes" : (Str)"No";
    if (str_is_empty(vote->comment)) {
	(void)fprintf(out_file, "<DT>`%s' votes `%s' with no comment.\n",
	  vote->email, vote_str);
    } else {
	(void)fprintf(out_file,
	  "<DT>`%s' votes `%s' with the following comment:\n"
	  "<DD>%s\n",
	  vote->email, vote_str, vote->comment);
    }
}

/*
 * votes_append(votes, vote)
 *	This routine will append {vote} to {votes}.
 */
static void
votes_append(
    Votes votes,
    Vote vote)
{
    vector_append((Vector)votes, (Pointer)vote);
}

/*
 * votes_create()
 *	This routine will create and return an empty votes vector.
 */
Votes
votes_create(void)
{
    Votes votes;

    votes = (Votes)vector_create();
    return votes;
}

/*
 * votes_size(votes)
 *	This routine will return the number of {Vote} objects in {votes}.
 */
unsigned
votes_size(
    Votes votes)
{
    return vector_size((Vector)votes);
}

/*
 * votes_update(link, response_file, vote_str, comment_str, email_str, errors)
 *	This routine will find the vote corresponding to {email_str} in the
 *	votes attached to {link.  If found, the vote's contents will be
 *	updated to contain {vote_str} and {comment_str}; otherwise, a
 *	new vote will be created and appended to {link}'s votes.  An HTML
 *	response message is output to {response_file}.  Any errors are
 *	reported via {errors}.
 */
int
votes_update(
    Link link,
    FILE *response_file,
    Str vote_str,
    Str comment_str,
    Str email_str,
    Errors errors)
{
    int do_save;
    unsigned index;
    Vote vote;
    Votes votes;

    do_save = 1;
    votes = link_votes(link);
    index = 0;
    VOTES_LOOP(vote, votes) {
	if (str_equal(email_str, vote->email)) {
	    break;
	}
	index++;
    }
    if (vote == (Vote)0) {
	/* Create vote */
	if (str_equal(vote_str, (Str)"Yes") ||
	  str_equal(vote_str, (Str)"No")) {
	    vote = vote_create(link);
	    vote->yes = str_equal(vote_str, (Str)"Yes");
	    vote->comment = comment_str;
	    vote->email = email_str;
	    votes_append(votes, vote);
	    (void)fprintf(response_file,
	      "Your vote has been appended to the voting log as:\n"
	      "<DL>\n");
	    vote_write_html(vote, response_file);
	    (void)fprintf(response_file, "</DL>\n");
	} else {
	    errors_append(errors,
	      str_printf("No vote matches `%s'.", email_str));
	}
    } else {
	/* Update vote */
	if (str_equal(vote_str, (Str)"Yes") ||
	  (str_equal(vote_str, (Str)"No"))) {
	    FILE *mail_file;
	    Str command;

	    vote->yes = str_equal(vote_str, (Str)"Yes");
	    vote->comment = comment_str;
	    vote->email = email_str;
	    (void)fprintf(response_file,
	      "Your vote has been updated to look as follows:\n"
	      "<DL>\n");
	    vote_write_html(vote, response_file);
	    (void)fprintf(response_file, "</DL>\n");

	    /* Mail a notification: */
	    command = str_printf("mailx -n -s \"Vote Notification\"", "w");
	    mail_file = popen((char *)command, "w");
	    if (mail_file == (FILE *)0) {
		/* Mail notification failed: */
		(void)fprintf(response_file, "Mail notification failed!\n");
	    } else {
		(void)fprintf(mail_file,
		  "A vote on\n"
		  "\t%s\n"
		  "was registered in your name.\n",
		  url_str(remote_url(link_remote(vote->link))));
		(void)fclose(mail_file);
	    }
	} else if (str_equal(vote_str, (Str)"View")) {
	    (void)fprintf(response_file,
	      "The vote posted by `%s' looks as follows:\n"
	      "<DL>\n",
	      email_str);
	    vote_write_html(vote, response_file);
	    (void)fprintf(response_file, "</DL>\n");
	    do_save = 0;
	} else if (str_equal(vote_str, (Str)"Delete")) {
	    votes_delete(votes, index);
	    (void)fprintf(response_file,
	      "The following vote has been deleted:\n"
	      "<DL>\n");
	    vote_write_html(vote, response_file);
	    (void)fprintf(response_file, "</DL>\n");
	}
    }
    if (errors_size(errors) != 0) {
	/* Database is probably somewhat corrupted. */
	do_save = 0;
    }
    return do_save;
}

/*
 * votes_write(remotes, config, errors)
 *	This routine will write out each of the vote files associated with
 *	{remotes}.
 */
void
votes_write(
    Remotes remotes,
    Config config,
    Errors errors)
{
    Remote remote;

    REMOTES_LOOP(remote, remotes) {
	Link link;
	Links links;
	Url url;

	url = remote_url(remote);
	links = remote_links(remote);
	LINKS_LOOP(link, links) {
	    Url local_url;
	    unsigned total;
	    Vote vote;
	    Url vote_url;
	    FILE *vote_file;
	    Str vote_file_name;
	    Votes votes;
	    unsigned yes;

	    local_url = link_href(link);
	    vote_url = link_vote_url(link, config);
	    vote_file_name = url_str_local_path(vote_url, config);
	    make_path(vote_file_name);
	    vote_file = fopen((char *)vote_file_name, "w");
	    str_free(vote_file_name);
	    if (vote_file == (FILE *)0) {
		Str message;

		message = str_printf("Could not create vote file '%s'!",
		  vote_file_name);
		errors_append(errors, message);
		str_free(vote_file_name);
	        return;
	    }
	    str_free(vote_file_name);

	    (void)fprintf(vote_file,
	      "<HTML>\n"
	      "<Head>\n"
	      "<Title>Vote on Reference</Title>"
	      "</Head>\n"
	      "<Body>\n"
	      "<H1>Vote on Reference</H1>");

	    /* Count of the votes: */
	    votes = link_votes(link);
	    total = votes_size(votes);
	    yes = votes_yes(votes);

	    (void)fprintf(vote_file,
	      "<Form Method=POST action=\"http://%s/cgi-bin/post_vote\">\n",
	      config_host_name(config));
	    (void)fprintf(vote_file,
	      "If you wish to vote on this reference,\n"
	      "please fill out and submit the form below:\n"
	      "<Br>\n"
	      "<input Type=\"radio\" Name=\"Vote\" Value=\"Yes\" Checked>\n"
	      "Vote `Yes':\n"
	      "Most other people would find this reference interesting.\n"
	      "<Br>\n"
	      "<input Type=\"radio\" Name=\"Vote\" Value=\"No\">\n"
	      "Vote `No':\n"
	      "Most other people would find this reference uninteresting.\n"
	      "<Br>\n"
	      "<input Type=\"radio\" Name=\"Vote\" Value=\"View\">\n"
	      "View:\n"
	      "Show vote associated with E-mail address.\n"
	      "<Br>\n"
	      "<input Type=\"radio\" Name=\"Vote\" Value=\"Delete\">\n"
	      "Delete:\n"
	      "Remove vote associated with E-mail address.\n"
	      "<P>\n");
	    (void)fprintf(vote_file,
	      "Your full E-mail address is required:\n"
	      "<Br>\n"
	      "<input Type=\"text\" Name=\"Email\" Size=\"60\">\n"
	      "<Br>\n"
	      "The desired policy for voting on references is\n"
	      "`One person, one vote'.  This policy is closely\n"
	      "approximated by associating each reference vote\n"
	      "with an E-mail address.  Each time a vote is\n"
	      "submitted, a message containing the relevant voting\n"
	      "information is E-mailed to the voter's supplied\n"
	      "E-mail address.\n"
	      "<P>\n");
	    (void)fprintf(vote_file,
	      "A terse one-line comment is optional:\n"
	      "<Br>\n"
	      "<input Type=\"text\" Name=\"Comment\" Size=\"60\">\n"
	      "<Br>\n"
	      "To make voting a little more interesting, you may also\n"
	      "submit a terse one-line comment about the reference\n"
	      "that will be displayed as part of the voting record.\n"
	      "<P>\n");
	    (void)fprintf(vote_file,
	      "<input Type=\"hidden\" Name=\"RemoteURL\" Value=\"");
	    url_write(url, vote_file);
	    (void)fprintf(vote_file,
	      "\">\n"
	      "<Br>\n");
	    (void)fprintf(vote_file,
	      "<input Type=\"hidden\" Name=\"LocalURL\" Value=\"");
	    url_write(local_url, vote_file);
	    (void)fprintf(vote_file,
	      "\">\n"
	      "<Br>\n");
	    (void)fprintf(vote_file,
	      "<input Type=\"submit\" Value=\"Submit Vote\">\n");
	    (void)fprintf(vote_file,
	      "</Form>\n");

	    (void)fprintf(vote_file, "<HR>\n");
	    if (total == 0) {
		(void)fprintf(vote_file,
		  "No votes registered on this reference yet.\n");
	    } else {
		(void)fprintf(vote_file,
		  "There are %u `Yes' and %u `No' votes "
		  "for a ratio of %u%%.\n", yes,
		  total - yes, yes *100 / total);
		(void)fprintf(vote_file, "<DL>\n");
		votes = link_votes(link);
		VOTES_LOOP(vote, votes) {
		    vote_write_html(vote, vote_file);
		}
		(void)fprintf(vote_file, "</UL>\n");
	    }

	    (void)fprintf(vote_file, "</Body>\n");
	    (void)fprintf(vote_file, "</HTML>\n");
	    (void)fclose(vote_file);
	}
    }
}

/*
 * votes_yes(votes)
 *	This routine will return the number of yes votes in {votes}.
 */
unsigned
votes_yes(
    Votes votes)
{
    Vote vote;
    unsigned yes;

    yes = 0;
    VOTES_LOOP(vote, votes) {
	if (vote->yes) {
	    yes += 1;
	}
    }
    return yes;
}

