//
// The Antville Project
// http://code.google.com/p/antville
//
// Copyright 2001-2007 by The Antville People
//
// Licensed under the Apache License, Version 2.0 (the ``License'');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an ``AS IS'' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// $Revision$
// $LastChangedBy$
// $LastChangedDate$
// $URL$
//
/**
 * @fileOverview Defines the Site prototype.
 */
this.handleMetadata("archiveMode");
this.handleMetadata("commentMode");
this.handleMetadata("configured");
this.handleMetadata("locale");
this.handleMetadata("longDateFormat");
this.handleMetadata("notificationMode");
this.handleMetadata("notifiedOfBlocking");
this.handleMetadata("notifiedOfDeletion");
this.handleMetadata("closed");
this.handleMetadata("pageSize");
this.handleMetadata("pageMode");
this.handleMetadata("shortDateFormat");
this.handleMetadata("tagline");
this.handleMetadata("timeZone");
this.handleMetadata("title"),
this.handleMetadata("callbackMode");
this.handleMetadata("callbackUrl");
this.handleMetadata("spamfilter");
/**
 * @function
 * @returns {String[]}
 * @see defineConstants
 */
Site.getStatus = defineConstants(Site, "blocked", "regular", "trusted");
/**
 * @function
 * @returns {String[]}
 * @see defineConstants
 */
Site.getModes = defineConstants(Site, "closed", "restricted", "public", "open");
/**
 * @function
 * @returns {String[]}
 * @see defineConstants
 */
Site.getPageModes = defineConstants(Site, "stories"); //, "days");
/**
 * @function
 * @returns {String[]}
 * @see defineConstants
 */
Site.getCommentModes = defineConstants(Site, "disabled", "enabled");
/**
 * @function
 * @returns {String[]}
 * @see defineConstants
 */
Site.getArchiveModes = defineConstants(Site, "closed", "public");
/**
 * @function
 * @returns {String[]}
 * @see defineConstants
 */
Site.getNotificationModes = defineConstants(Site, "Nobody", 
      "Owner", "Manager", "Contributor", "Subscriber" );
/**
 * @function
 * @returns {String[]}
 * @see defineConstants
 */
Site.getCallbackModes = defineConstants(Site, "disabled", "enabled");
/**
 * 
 * @param {Site} site
 */
Site.remove = function(site) {
   HopObject.remove(site.members);
   HopObject.remove(site.stories);
   HopObject.remove(site.images);
   HopObject.remove(site.files);
   HopObject.remove(site.polls);
   site.layout && Layout.remove.call(site.layout);
   site.getStaticFile().removeDirectory();
   site.remove();
   // FIXME: There is a problem in the log structure for a deleted site
   root.admin.log(root, "Removed site " + site.name);
   return;
}
/**
 * 
 * @param {String} name
 * @returns {Site}
 */
Site.getByName = function(name) {
   return root.get(name);
}
/**
 * 
 * @param {String} mode
 * @returns {Boolean}
 */
Site.require = function(mode) {
   var modes = [Site.CLOSED, Site.RESTRICTED, Site.PUBLIC, Site.OPEN];
   return modes.indexOf(res.handlers.site.mode) >= modes.indexOf(mode);
}
/**
 * A Site object is the basic container of Antville.
 * @name Site
 * @constructor
 * @param {String} name A unique identifier also used in the URL of a site
 * @param {String} title An arbitrary string branding a site
 * @property {Tag[]} $tags
 * @property {Archive} archive
 * @property {String} archiveMode The way the archive of a site is displayed
 * @property {String} commentMode The way comments of a site are displayed
 * @property {Date} created The date and time of site creation
 * @property {User} creator A reference to a user who created a site
 * @property {Tags} galleries
 * @property {Files} files
 * @property {Images} images
 * @property {Layout} layout
 * @property {String} locale The place and language settings of a site
 * @property {String} longDateFormat The long date format string
 * @property {Members} members
 * @property {Metadata} metadata
 * @property {String} mode The access level of a site
 * @property {Date} modified The date and time when a site was last modified
 * @property {User} modifier A reference to a user who modified a site
 * @property {String} notificationMode The way notifications are sent from a site
 * @property {String} pageMode The way stories of a site are displayed
 * @property {Number} pageSize The amount of stories to be displayed simultaneously
 * @property {Polls} polls
 * @property {String} shortDateFormat The short date format string
 * @property {String} status The trust level of a site
 * @property {Stories} stories
 * @property {String} tagline An arbitrary text describing a site
 * @property {Tags} tags
 * @property {String} timeZone The time and date settings of a site
 * @extends HopObject
 */
Site.prototype.constructor = function(name, title) {
   var now = new Date;
   var locale = root.getLocale();
   var user = session.user || new HopObject;
   this.map({
      name: name,
      title: title || name,
      created: now,
      creator: user,
      modified: now,
      modifier: user,
      status: user.status === User.PRIVILEGED ? Site.TRUSTED : user.status,
      mode: Site.CLOSED,
      tagline: String.EMPTY,
      callbackEnabled: false,
      commentMode: Site.OPEN,
      archiveMode: Site.PUBLIC,
      notificationMode: Site.DISABLED,
      pageMode: Site.DAYS,
      pageSize: 3,
      locale: locale.toString(),
      timeZone: root.getTimeZone().getID(),
      longDateFormat: LONGDATEFORMAT,
      shortDateFormat: SHORTDATEFORMAT
   });
   return this;
}
/**
 * 
 * @param {String} action
 * @returns {Boolean}
 */
Site.prototype.getPermission = function(action) {
   switch (action) {
      case "backup.js":
      case "main.js":
      case "main.css":
      case "error":
      case "notfound":
      case "robots.txt":
      case "search":
      case "search.xml":
      case "user.js":
      return true;
      case ".":
      case "main":
      case "comments.xml":
      case "rss.xml":
      case "rss.xsl":
      case "stories.xml":
      return Site.require(Site.PUBLIC) ||
            (Site.require(Site.RESTRICTED) && 
            Membership.require(Membership.CONTRIBUTOR)) ||
            (Site.require(Site.CLOSED) &&
            Membership.require(Membership.OWNER)) ||
            User.require(User.PRIVILEGED);
      case "edit":
      case "export":
      case "referrers":
      return Membership.require(Membership.OWNER) ||
            User.require(User.PRIVILEGED);
      case "subscribe":
      return Site.require(Site.PUBLIC) &&
            !Membership.require(Membership.SUBSCRIBER);
      case "unsubscribe":
      if (User.require(User.REGULAR)) {
         var membership = Membership.getByName(session.user.name);
         return membership && !membership.require(Membership.OWNER);
      }
   }
   return false;
}
Site.prototype.main_action = function() {
   res.data.body = this.renderSkinAsString("Site#main");
   res.data.title = this.title;
   this.renderSkin("Site#page");
   this.log();
   return;
}
Site.prototype.edit_action = function() {
   if (req.postParams.save) {
      try {
         this.update(req.postParams);
         res.message = gettext("The changes were saved successfully.");
         res.redirect(this.href(req.action));
      } catch (ex) {
         res.message = ex;
         app.log(ex);
         /*writeln("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
         var e = new Packages.org.mozilla.javascript.EvaluatorException(ex);
         e.fillInStackTrace();
         res.debug(e.getScriptStackTrace());
         res.debug(e.printStackTrace(java.lang.System.out));
         var trace = e.getStackTrace();
         writeln(trace.toString());
         for (var i in ex)
         app.log(i + ": " + ex[i]);*/
      }
   }
   res.data.action = this.href(req.action);
   res.data.title = gettext("Preferences of {0}", this.title);
   res.data.body = this.renderSkinAsString("$Site#edit");
   this.renderSkin("Site#page");
   return;
}
/**
 * 
 * @param {String} name
 * @returns {Object}
 */
Site.prototype.getFormOptions = function(name) {
   switch (name) {
      case "archiveMode":
      return Site.getArchiveModes();
      case "commentMode":
      return Site.getCommentModes();
      case "locale":
      return getLocales();
      case "layout":
      return this.getLayouts();
      case "longDateFormat":
      return getDateFormats("long");
      case "mode":
      return Site.getModes();
      case "notificationMode":
      return Site.getNotificationModes();
      case "pageMode":
      return Site.getPageModes();
      case "status":
      return Site.getStatus();
      case "shortDateFormat":
      return getDateFormats("short");
      case "timeZone":
      return getTimeZones();
      case "callbackMode":
      return Site.getCallbackModes();
      default:
      return HopObject.prototype.getFormOptions.apply(this, arguments);
   }
}
/**
 * 
 * @param {Object} data
 */
Site.prototype.update = function(data) {
   if (this.isTransient()) {
      if (!data.name) {
         throw Error(gettext("Please enter a name for your new site."));
      } else if (data.name.length > 30) {
         throw Error(gettext("The chosen name is too long. Please enter a shorter one."));
      } else if (/(\/|\\)/.test(data.name)) {
         throw Error(gettext("A site name may not contain any (back)slashes."));
      } else if (data.name !== root.getAccessName(data.name)) {
         throw Error(gettext("There already is a site with this name."));
      }
      this.layout = new Layout(this);
      this.name = data.name;
      this.title = data.title || data.name;
      return;
   }
   this.map({
      title: stripTags(data.title) || this.name,
      tagline: data.tagline,
      mode: data.mode || Site.PRIVATE,
      callbackUrl: data.callbackUrl,
      callbackMode: data.callbackMode || Site.DISABLED,
      pageMode: data.pageMode || Site.DAYS,
      pageSize: parseInt(data.pageSize, 10) || this.pageSize || 3,
      commentMode: data.commentMode || Site.DISABLED,
      archiveMode: data.archiveMode || Site.CLOSED,
      notificationMode: data.notificationMode || Site.DISABLED,
      timeZone: data.timeZone,
      longDateFormat: data.longDateFormat,
      shortDateFormat: data.shortDateFormat,
      locale: data.locale,
      spamfilter: data.spamfilter
   });
   this.configured = new Date;
   this.modifier = session.user;
   this.clearCache();
   return;
}
Site.prototype.main_css_action = function() {
   res.dependsOn(this.modified);
   res.dependsOn(Skin("Site", "values").getSource());
   res.dependsOn(Skin("Site", "stylesheet").getSource());
   res.digest();
   res.contentType = "text/css";
   this.renderSkin("Site#stylesheet");
   return;
}
Site.prototype.main_js_action = function() {
   res.contentType = "text/javascript";
   for each (script in ["jquery-1.2.3.min.js", "antville-1.2.js"]) {
      this.renderSkin("$Site#include", {href: root.getStaticUrl(script)});
   }
   this.renderSkin("$Site#include", {href: this.href("user.js")});
   return;
}
Site.prototype.user_js_action = function() {
   res.contentType = "text/javascript";
   res.dependsOn(this.modified);
   res.dependsOn(Skin("Site", "javascript").getSource());
   res.digest();
   this.renderSkin("Site#javascript");
   return;  
}
Site.prototype.backup_js_action = function() {
   if (req.isPost()) {
      session.data.backup = {};
      for (var key in req.postParams) {
         session.data.backup[key] = req.postParams[key];
      }
   }
   return;
}
Site.prototype.rss_xml_action = function() {
   res.dependsOn(this.modified);
   res.digest();
   res.contentType = "text/xml";
   res.write(this.getXml(this.stories.union));
   return;
}
Site.prototype.stories_xml_action = function() {
   res.dependsOn(this.modified);
   res.digest();
   res.contentType = "text/xml";
   res.write(this.getXml(this.stories.recent));
   return;
}
Site.prototype.comments_xml_action = function() {
   res.dependsOn(this.modified);
   res.digest();
   res.contentType = "text/xml";
   res.write(this.getXml(this.stories.comments));
   return;
}
Site.prototype.search_xml_action = function() {
   return; // FIXME
   res.contentType = "application/opensearchdescription+xml";
   res.write(
      	Antville Search
      	Search on Antville
      	antville search
      	http://www.youtube.com/favicon.ico
      	
      	
         );
   return;   
}
/**
 * 
 * @param {Story[]} collection
 */
Site.prototype.getXml = function(collection) {
   collection || (collection = this.stories.recent);
   var now = new Date;
   var feed = new rome.SyndFeedImpl();   
   feed.setFeedType("rss_2.0");
   feed.setLink(this.href());
   feed.setTitle(this.title);
   feed.setDescription(this.tagline || String.EMPTY);
   feed.setLanguage(this.locale.replace("_", "-"));
   feed.setPublishedDate(now);
   /*
   var feedInfo = new rome.FeedInformationImpl();
   var feedModules = new java.util.ArrayList();
   feedModules.add(feedInfo);
   feed.setModules(feedModules);
   //feedInfo.setImage(new java.net.URL(this.getProperty("imageUrl")));
   feedInfo.setSubtitle(this.tagline);
   feedInfo.setSummary(this.description);
   feedInfo.setAuthor(this.creator.name);
   feedInfo.setOwnerName(this.creator.name);
   //feedInfo.setOwnerEmailAddress(this.getProperty("email"));
   */
   var entry, entryInfo, entryModules;
   var enclosure, enclosures, keywords;
   var entries = new java.util.ArrayList();
   var description;
   var list = collection.constructor === Array ? 
         collection : collection.list(0, 25);
   for each (var item in list) {
      entry = new rome.SyndEntryImpl();
      item.title && entry.setTitle(item.title);
      entry.setLink(item.href());
      entry.setAuthor(item.creator.name);
      entry.setPublishedDate(item.created);
      if (item.text) {
         // FIXME: Work-around for "story" handlers in comment skins
         // (Obsolete as soon as "story" handlers are replaced with "this")
         //res.handlers.story = item;
         description = new rome.SyndContentImpl();
         //description.setType("text/plain");
         // FIXME: Work-around for org.jdom.IllegalDataException caused by some ASCII control characters 
         description.setValue(item.renderSkinAsString("Story#rss").replace(/[\x00-\x1f^\x0a^\x0d]/g, function(c) {
            return "" + c.charCodeAt(0) + ";";
         }));
         entry.setDescription(description);
      }
      entries.add(entry);
      
      /*
      entryInfo = new rome.EntryInformationImpl();
      entryModules = new java.util.ArrayList();
      entryModules.add(entryInfo);
      entry.setModules(entryModules);
      enclosure = new rome.SyndEnclosureImpl();
      enclosure.setUrl(episode.getProperty("fileUrl"));
      enclosure.setType(episode.getProperty("contentType"));
      enclosure.setLength(episode.getProperty("filesize") || 0);
      enclosures = new java.util.ArrayList();
      enclosures.add(enclosure);
      entry.setEnclosures(enclosures);
      entryInfo.setAuthor(entry.getAuthor());
      entryInfo.setBlock(false);
      entryInfo.setDuration(new rome.Duration(episode.getProperty("length") || 0));
      entryInfo.setExplicit(false);
      entryInfo.setKeywords(episode.getProperty("keywords"));
      entryInfo.setSubtitle(episode.getProperty("subtitle"));
      entryInfo.setSummary(episode.getProperty("description"));
      */
   }
   feed.setEntries(entries);
   
   var output = new rome.SyndFeedOutput();
   //output.output(feed, res.servletResponse.writer); return;
   var xml = output.outputString(feed);
   // FIXME: Ugly hack for adding PubSubHubbub and rssCloud elements to XML
   xml = xml.replace("", '\n    ');
   xml = xml.replace("", '\n    '); 
   return xml; //injectXslDeclaration(xml);
}
Site.prototype.rss_xsl_action = function() {
   res.charset = "UTF-8";
   res.contentType = "text/xml";
   renderSkin("Global#xslStylesheet");
   return;
}
Site.prototype.referrers_action = function() {
   if (req.data.permanent && this.getPermission("edit"))  {
      var urls = req.data.permanent_array;
      res.write(this.metadata.get("spamfilter"));
      for (var i in urls) {
         res.write("\n");
         res.write(urls[i].replace(/\?/g, "\\\\?"));
      }
      this.metadata.set("spamfilter", res.pop());
      res.redirect(this.href(req.action));
      return;
   }
   res.data.action = this.href(req.action);
   res.data.title = gettext("Referrers in the last 24 hours of {0}", this.title);
   res.data.body = this.renderSkinAsString("$Site#referrers");
   this.renderSkin("Site#page");
   return;
}
Site.prototype.search_action = function() {
   var search;
   if (!(search = req.data.q) || !stripTags(search)) {
      res.message = gettext("Please enter a query in the search form.");
      res.data.body = this.renderSkinAsString("Site#search");
   } else {
      // Prepare search string for metadata: Get source and remove 
      // '(new String("..."))' wrapper; finally, double all backslashes
      search = String(search).toSource().slice(13, -3).replace(/(\\)/g, "$1$1");
      var title = '%title:"%' + search + '%"%';
      var text = '%text:"%' + search + '%"%';
      var sql = new Sql();
      sql.retrieve("select id from content where site_id = $0 and " +
            "prototype = $1 and status <> $2 and (metadata like $3 or " +
            "metadata like $4) order by created desc limit $5", 
            this._id, "Story", Story.CLOSED, text, title, 25);
      res.push();
      var counter = 0;
      sql.traverse(function() {
         var story = Story.getById(this.id);
         story.renderSkin("Story#result");
         counter += 1;
      });
      res.message = ngettext("Found {0} result.", 
            "Found {0} results.", counter);
      res.data.body = res.pop();
   }
   
   res.data.title = gettext('Search results for "{0}" in site "{1}"', 
         search, this.title);
   this.renderSkin("Site#page");
   return;
}
Site.prototype.subscribe_action = function() {
   try {
      var membership = new Membership(session.user, Membership.SUBSCRIBER);
      this.members.add(membership);
      res.message = gettext('Successfully subscribed to site {0}.', 
            this.title);
   } catch (ex) {
      app.log(ex);
      res.message = ex.toString();
   }
   res.redirect(this.href());
   return;
}
Site.prototype.unsubscribe_action = function() {
   if (req.postParams.proceed) {
      try {
         Membership.remove(Membership.getByName(session.user.name));
         res.message = gettext("Successfully unsubscribed from site {0}.",
               this.title);
         res.redirect(User.getLocation() || this.href());
      } catch (ex) {
         app.log(ex)
         res.message = ex.toString();
      }
   }
   User.setLocation();
   res.data.title = gettext("Remove subscription to {0}", this.title);
   res.data.body = this.renderSkinAsString("$HopObject#confirm", {
      text: gettext('You are about to unsubscribe from site {0}.', this.title)
   });
   this.renderSkin("Site#page");
   return;
}
Site.prototype.export_action = function() {
   var fname = this.name + "-export.zip";
   var zip = new helma.File(this.getStaticFile(fname));
   if (req.postParams.submit === "export") {
      if (Exporter.add(this)) {
         res.message = "Site is queued for export";
         zip.remove();
      } else {
         res.message = "Site is already being exported";
      }
      res.redirect(this.href(req.action));
   }
   var param = {
      fileName: zip.getName(),
      fileUrl: zip.exists() ? this.getStaticUrl(zip.getName()) : null,
      fileDate: new Date(zip.lastModified())
   }
   res.data.body = this.renderSkinAsString("$Site#export", param);
   this.renderSkin("Site#page");
   return;
}
Site.prototype.robots_txt_ction = function() {
   res.contentType = "text/plain";
   this.renderSkin("Site#robots");
   return;
}
/**
 * 
 * @param {String} name
 * @returns {HopObject}
 */
Site.prototype.getMacroHandler = function(name) {
   switch (name) {
      case "archive":
      case "files":
      case "galleries":
      case "images":
      case "layout":
      case "members":
      case "polls":
      case "stories":
      case "tags":
      return this[name];
      default:
      return null;
   }
}
/**
 * 
 */
Site.prototype.stories_macro = function() {
   if (this.stories.featured.size() < 1) {
      this.renderSkin("Site#welcome");
      if (session.user) {
         if (session.user === this.creator) {
            session.user.renderSkin("$User#welcome");
         }
         if (this === root && User.require(User.PRIVILEGED)) {
            this.admin.renderSkin("$Admin#welcome");
         }
      }
   } else {
      this.archive.renderSkin("Archive#main");
   }
   return;
}
/**
 * 
 * @param {Object} param
 */
Site.prototype.calendar_macro = function(param) {
   if (this.archiveMode !== Site.PUBLIC) {
      return;
   }
   var calendar = new jala.Date.Calendar(this.archive);
   //calendar.setAccessNameFormat("yyyy/MM/dd");
   calendar.setHrefFormat("/yyyy/MM/dd/");
   calendar.setLocale(this.getLocale());
   calendar.setTimeZone(this.getTimeZone());
   calendar.render(this.archive.getDate());
   return;
}
/**
 * 
 * @param {Object} param
 */
Site.prototype.age_macro = function(param) {
   res.write(Math.floor((new Date() - this.created) / Date.ONEDAY));
   return;
}
/**
 * 
 */
Site.prototype.referrers_macro = function() {
   var self = this;
   var sql = new Sql();
   sql.retrieve("select referrer, count(*) as requests from " +
         "log where context_type = 'Site' and context_id = $0 and action = " +
         "'main' and created > date_add(now(), interval -1 day) group " +
         "by referrer order by requests desc, referrer asc", this._id);
   sql.traverse(function() {
      if (this.requests && this.referrer) {
         this.text = encode(this.referrer.head(50));
         this.referrer = encode(this.referrer);
         self.renderSkin("$Site#referrer", this);
      }
   });
   return;
}
/**
 * @returns {java.util.Locale}
 */
Site.prototype.getLocale = function() {
   var locale;
   if (locale = this.cache.locale) {
      return locale;
   } else if (this.locale) {
      var parts = this.locale.split("_");
      locale = new java.util.Locale(parts[0] || String.EMPTY, 
            parts[1] || String.EMPTY, parts.splice(2).join("_"));
   } else {
      locale = java.util.Locale.getDefault();
   }
   return this.cache.locale = locale;
}
/**
 * @returns {java.util.TimeZone}
 */
Site.prototype.getTimeZone = function() {
   var timeZone;
   if (timeZone = this.cache.timeZone) {
      return timeZone;
   }
   if (this.timeZone) {
       timeZone = java.util.TimeZone.getTimeZone(this.timeZone);
   } else {
       timeZone = java.util.TimeZone.getDefault();
   }
   this.cache.timezone = timeZone;
   return timeZone;
}
/**
 * 
 * @param {String} href
 */
Site.prototype.processHref = function(href) {
   var vhost = getProperty("vhost." + this.name, 
         app.properties.defaultHost + "/" + this.name);
   return vhost + href;
}
/**
 * 
 * @param {String} type
 * @param {String} group
 * @returns {Tag[]}
 */
Site.prototype.getTags = function(type, group) {
   var handler;
   type = type.toLowerCase();
   switch (type) {
      case "story":
      case "tags":
      handler = this.stories;
      type = "tags";
      break;
      case "image":
      case "galleries":
      handler = this.images;
      type = "galleries";
      break;
   }
   switch (group) {
      case Tags.ALL:
      return handler[type];     
      case Tags.OTHER:
      case Tags.ALPHABETICAL:
      return handler[group + type.titleize()];
      default:
      return handler["alphabetical" + type.titleize()].get(group);
   }
   return null;
}
/**
 * 
 * @param {String} tail
 * @returns {helma.File}
 */
Site.prototype.getStaticFile = function(tail) {
   res.push();
   res.write(app.properties.staticPath);
   res.write(this.name);
   res.write("/");
   tail && res.write(tail);
   return new helma.File(res.pop());
}
/**
 * 
 * @param {String} tail
 * @returns {String}
 */
Site.prototype.getStaticUrl = function(tail) {
   res.push();
   res.write(app.properties.staticUrl);
   res.write(this.name);
   res.write("/");
   tail && res.write(tail);
   return encodeURI(res.pop());
}
/**
 * 
 * @param {Object} ref
 */
Site.prototype.callback = function(ref) {
    if (this.callbackMode === Site.ENABLED && this.callbackUrl) {
      app.data.callbacks.push({
         site: this._id,
         handler: ref.constructor,
         id: ref._id
      });
   }
   return;
}
/**
 * 
 * @param {String} name
 * @returns {String[]}
 */
Site.prototype.getAdminHeader = function(name) {
   switch (name) {
      case "tags":
      case "galleries":
      return ["#", "Name", "Items"];
   }
   return [];
}