/* @(#)remote.c 1.6 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 "assert.h"
#include "config_extern.h"
#include "error_extern.h"
#include "html_extern.h"
#include "local_extern.h"
#include "link_extern.h"
#include "memory_extern.h"
#include "remote_extern.h"
#include "str_extern.h"
#include "text_extern.h"
#include "vector_extern.h"
#include "url_extern.h"

struct remote_struct {
    int is_dummy;	/* 1=>dummy remote object; 0=>the real thing. */
    Links links;	/* List of hyper text links in document */
    unsigned timestamp;	/* Timestamp of when document was accessed */
    Str title;		/* Document title */
    Url url;		/* Url for remote document */
};

static int remote_is_dummy(Remote);

/*
 * remote_create(url)
 *	This routine will create and return a {Remote} object
 *	that contains {url} and all other fields zeroed out.
 */
static Remote
remote_create(
    Url url)
{
    Remote remote;

    remote = (Remote)memory_allocate_zeroed(sizeof *remote);
    remote->is_dummy = 0;
    remote->url = url;
    remote->links = links_create();
    return remote;
}

/*
 * remote_create_dummy()
 *	This routine will create a dummy {Remote} object and return
 *	it.  It is useful to implementing the {post_solicit} program.
 */
Remote
remote_create_dummy(void)
{
    Remote remote;
    Url url;

    url = url_parse((Str)"http://_/_.html");
    remote = remote_create(url);
    remote->is_dummy = 1;
    return remote;
}

/*
 * remote_is_dummy(remote)
 *	This routine will return 1 if {remote} is a dummy remote object
 *	(created by {remote_create_dummy}(); otherwise, 0 is returned.
 */
static int
remote_is_dummy(
    Remote remote)
{
    return remote->is_dummy;
}

/*
 * remote_links(remote)
 *	This routine will return the links associated with {remote}.
 */
Links
remote_links(
    Remote remote)
{
    return remote->links;
}

/*
 * remote_restore(in_file, errors)
 *	This routine will read a {Remote} object from {in_file} that was
 *	written by {remote_save()} and return it.
 */
static Remote
remote_restore(
    FILE *in_file,
    Errors errors)
{
    Remote remote;
    Url url;

    remote = (Remote)0;
    if (getc(in_file) != 'R') {
	goto error;
    }
    if (getc(in_file) != ' ') {
	goto error;
    }
    url = url_restore(in_file);
    remote = remote_create(url);
    if (getc(in_file) != ' ') {
	goto error;
    }
    (void)fscanf(in_file, "%u", &remote->timestamp);
    if (getc(in_file) != ' ') {
	goto error;
    }
    remote->title = str_restore(in_file);
    if (getc(in_file) != '\n') {
	goto error;
    }
    remote->links = links_restore(remote, in_file, errors);
    return remote;
  error:
    errors_append(errors, (Str)"Trouble restoring remote object.");
    return remote;
}

/*
 * remote_save(remote, out_file)
 *	This routine will write {remote} to {out_file} in such a way that it
 *	can be reread via remote_restore().
 */
static void
remote_save(
    Remote remote,
    FILE *out_file)
{
    (void)fprintf(out_file, "R ");
    url_save(remote->url, out_file);
    (void)fprintf(out_file, " %u ", remote->timestamp);
    str_save(remote->title, out_file);
    (void)fprintf(out_file, "\n");
    links_save(remote->links, out_file);
}

/*
 * remote_scan(remote_url_str, config, errors)
 *	This routine will read in the document named by {remote_url_str},
 *	scan it for hyptertext links to documents residing the machine
 *	named in {config}.  If any errors occur, {errors} is updated to
 *	explain what went wrong.
 */
Remote
remote_scan(
    Str remote_url_str,
    Config config,
    Errors errors)
{
    Link link;
    Links links;
    Remote remote;
    Html remote_html;
    Url remote_url;
    Tag tag;
    Tags tags;

    /* Slurp in the reference document: */
    remote_url = url_parse(remote_url_str);
    remote = remote_create(remote_url);
    remote_html = html_read(remote_url, errors);
    if (errors_size(errors) != 0) {
	return remote;
    }

    /* Search for all references to documents on current machine: */
    tags = html_tags(remote_html);
    remote->title = (Str)"";
    TAG_END_SEARCH(tag, tags, (Str)"TITLE") {
	remote->title = text_str_copy(tag_preceeding_text(tag));
	break;
    }

    links = links_create();
    remote->links = links;
    TAG_START_SEARCH(tag, tags, (Str)"A") {
	attributes_scan(tag_attributes(tag), remote, config);
    }

    /* If no reference documents found, return error message: */
    if (links_size(links) == 0) {
	Str message;

	message = str_printf("No hypertext links to documents on `%s' found!",
	  config_host_name(config));
	errors_append(errors, message);
	return remote;
    }

    LINKS_LOOP(link, links) {
	local_update(link_href(link), link_remote(link), config, errors);
    }
    return remote;
}

/*
 * remote_title(remote)
 *	This routine will return the title associated with {remote}.
 */
Str
remote_title(
    Remote remote)
{
    return remote->title;
}

/*
 * remote_url(remote)
 *	This routine will return the URL associated with {remote}.
 */
Url
remote_url(
    Remote remote)
{
    return remote->url;
}



/*
 * remotes_create()
 *	This routine will create an return a {Remotes} object.
 */
Remotes
remotes_create(void)
{
    return (Remotes)vector_create();
}

/*
 * remotes_append(remotes, remote)
 *	This routine will append {remote} to {remotes}.
 */
void
remotes_append(
    Remotes remotes,
    Remote remote)
{
    vector_append((Vector)remotes, (Pointer)remote);
}

/*
 * remotes_inline_search(remotes, html)
 *	This routine will use the URL of each {Remote} object in {remotes}
 *	search for inline patterns in {html}.  The list of pattern matches
 *	is returned.
 */
Matches
remotes_inline_search(
    Remotes remotes,
    Html html)
{
    Matches matches;
    Remote remote;

    matches = matches_create();
    REMOTES_LOOP(remote, remotes) {
	Link link;
	Links links;

	links = remote->links;
	LINKS_LOOP(link, links) {
	    Match match;
	    Str pattern;
	    Url url;

	    url = link_href(link);
	    pattern = url_anchor(url);
	    if (!str_is_empty(pattern)) {
		match = html_search(html, pattern, remote);
		if (match != (Match)0) {
		    matches_append(matches, match);
		}
	    }
	}
    }
    matches_sort(matches);
    return matches;
}

/*
 * remotes_merge(remotes, remote_new)
 *	This routine will merge {remote_new} into {remotes} saving
 *	any appropriate information from previos times.  The resulting
 *	merged database is returned.
 */
void
remotes_merge(
    Remotes remotes,
    Remote remote_new)
{
    Remote remote;

    REMOTES_LOOP(remote, remotes) {
	if (url_document_equal(remote->url, remote_new->url)) {
	    links_votes_merge(remote->links, remote_new->links);
	    remote->links = remote_new->links;
	    remote->timestamp = remote_new->timestamp;
	    return;
	}
    }
    if (!remote_is_dummy(remote_new)) {
	remotes_append(remotes, remote_new);
    }
}

/*
 * remotes_restore(db_file_name, errors)
 *	This routine will read all of the {Remote} objects contained in
 *	the file named {db_file_name} and return them.
 */
Remotes
remotes_restore(
    Str db_file_name,
    Errors errors)
{
    FILE *db_file;
    unsigned index;
    Remote remote;
    Remotes remotes;
    unsigned size;

    remotes = remotes_create();
    db_file = fopen((char *)db_file_name, "r");
    if (db_file == (FILE *)0) {
	return remotes;
    }

    (void)fscanf(db_file, "%u\n", &size);
    for (index = 0; index < size; index++) {
	remote = remote_restore(db_file, errors);
	remotes_append(remotes, remote);
    }
    return remotes;
}

/*
 * remotes_save(remotes, db_file_name, errors)
 *	This routine will write {remotes} to the file named {db_file_name}.
 */
void
remotes_save(
    Remotes remotes,
    Str db_file_name,
    Errors errors)
{
    FILE *db_file;
    Remote remote;

    db_file = fopen((char *)db_file_name, "w");
    if (db_file == (FILE *)0) {
	Str message;

	message = str_printf("Could not open file `%s'!", db_file_name);
	errors_append(errors, message);
	return;
    }
    
    (void)fprintf(db_file, "%u\n", remotes_size(remotes));
    REMOTES_LOOP(remote, remotes) {
	remote_save(remote, db_file);
    }
    (void)fclose(db_file);
}

/*
 * remotes_size(remotes)
 *	This routine will return the number of {Remote} objects in {remotes}.
 */
unsigned
remotes_size(
    Remotes remotes)
{
    unsigned size;

    size = vector_size((Vector)remotes);
    return size;
}

/*
 * remote_write(remote, annote_file, config)
 *	This routine will write {remote} to {annote_file} as some HTML.
 */
void
remote_write(
    Remote remote,
    FILE *annote_file,
    Config config)
{
    (void)fprintf(annote_file, "<DL>\n");
    links_write(remote->links, annote_file, 0, config);
    (void)fprintf(annote_file, "</DL>\n");
}

/*
 * remotes_write(remotes, annote_file, list_src, config)
 *	This routine will write {remotes} to {annote_file} as some HTML.
 *	If {list_src} is 1, the sources are listed; otherwise,
 *	the references are listed.
 */
void
remotes_write(
    Remotes remotes,
    FILE *annote_file,
    int list_src,
    Config config)
{
    Remote remote;

    (void)fprintf(annote_file, "<DL>\n");
    REMOTES_LOOP(remote, remotes) {
	links_write(remote->links, annote_file, list_src, config);
    }
    (void)fprintf(annote_file, "</DL>\n");
}

