/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2001-02 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.core.impl;

import java.io.*;
import java.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.util.StringUtil;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.core.internals.EnvEngine;
import com.silverwrist.venice.db.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.util.*;

class ContactInfoImpl implements ContactInfo, Stashable
{
  /*--------------------------------------------------------------------------------
   * "Hook" classes used to store an image in the image store and modify the image
   * URL prior to saving contact info
   *--------------------------------------------------------------------------------
   */

  abstract class ImageHook
  {
    protected String mimetype;
    protected int length;
    protected InputStream data;

    protected ImageHook(String mimetype, int length, InputStream data)
    {
      this.mimetype = mimetype;
      this.length = length;
      this.data = data;

    } // end constructor

    public abstract String doImage(Connection conn) throws SQLException;

  } // end class ImageHook

  class NewImageHook extends ImageHook
  {
    private String prefix;
    private short type;
    private int ownerid;

    public NewImageHook(String prefix, short type, int ownerid, String mimetype, int length, InputStream data)
    {
      super(mimetype,length,data);
      this.prefix = prefix;
      this.type = type;
      this.ownerid = ownerid;

    } // end constructor

    public String doImage(Connection conn) throws SQLException
    {
      int id = ImageStore.storeNewImage(conn,type,ownerid,mimetype,length,data);
      return prefix + id;

    } // end doImage

  } // end class NewImageHook

  class ExistingImageHook extends ImageHook
  {
    private int imgid;

    public ExistingImageHook(int imgid, String mimetype, int length, InputStream data)
    {
      super(mimetype,length,data);
      this.imgid = imgid;

    } // end constructor

    public String doImage(Connection conn) throws SQLException
    {
      ImageStore.replaceImage(conn,imgid,mimetype,length,data);
      return null;

    } // end doImage

  } // end class ExistingImageHook

  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Category logger = Category.getInstance(ContactInfoImpl.class);

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private int contactid;                 // ID of this contact record (-1 = new)
  private String given_name;             // given name ("first name")
  private String family_name;            // family name ("last name")
  private char middle_initial;           // middle initial (natch)
  private String prefix;                 // prefix (Mr., Ms., Prof., Dr., etc.)
  private String suffix;                 // suffix (Jr., III, etc.)
  private String company;                // company name
  private String addr1;                  // address line 1
  private String addr2;                  // address line 2
  private String locality;               // locality ("city")
  private String region;                 // region ("state")
  private String postal_code;            // postal code ("ZIP code")
  private String country;                // 2-character ISO 3166 country code
  private String phone;                  // voice phone number
  private String fax;                    // facsimile (fax) number
  private String mobile;                 // mobile phone (cellphone) number
  private String email;                  // email address
  private String photo_url;              // URL of user photo
  private String url;                    // URL of user's homepage
  private boolean private_addr;          // are addr1 and addr2 hidden?
  private boolean private_phone;         // are phone and mobile hidden?
  private boolean private_fax;           // is fax hidden?
  private boolean private_email;         // is email hidden?
  private int owner_uid;                 // UID that owns this contact record
  private int owner_cid;                 // community ID this contact record is in (-1 for none)
  private java.util.Date last_update;    // date of last update
  private boolean is_modified = false;   // have we modified this ContactInfo?
  private ImageHook image_hook = null;   // image hook object
  private boolean can_set_photo;         // can we set the user photo?

  /*--------------------------------------------------------------------------------
   * Constructors
   *--------------------------------------------------------------------------------
   */

  /**
   * Constructs a new blank <CODE>ContactInfoImpl</CODE> object.
   *
   * @param owner_uid UID that owns this object.
   */
  ContactInfoImpl(int owner_uid, boolean can_set_photo)
  {
    if (logger.isDebugEnabled())
      logger.debug("new ContactInfoImpl (empty) for UID = " + owner_uid);
    this.can_set_photo = can_set_photo;
    makeEmpty(owner_uid,-1);

  } // end constructor

  /**
   * Constructs a new blank <CODE>ContactInfoImpl</CODE> object.
   *
   * @param owner_uid UID that owns this object.
   * @param owner_cid Community ID that owns this object.
   */
  ContactInfoImpl(int owner_uid, int owner_cid, boolean can_set_photo)
  {
    if (logger.isDebugEnabled())
      logger.debug("new ContactInfoImpl (empty) for UID = " + owner_uid + ", Community ID = " + owner_cid);
    this.can_set_photo = can_set_photo;
    makeEmpty(owner_uid,owner_cid);

  } // end constructor

  /**
   * Loads a <CODE>ContactInfoImpl</CODE> object out of the database.
   *
   * @param env Engine environment to get a database connection from.
   * @param contactid ID of the contact to load.
   * @exception DataException The contact could not be loaded for some reason.
   */
  ContactInfoImpl(EnvEngine env, int contactid, boolean can_set_photo) throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("new ContactInfoImpl (loading CID " + contactid + ")");
    this.can_set_photo = can_set_photo;
    Connection conn = null;

    try
    { // get a connection and call loadData
      conn = env.getConnection();
      loadData(conn,contactid);

    } // end try
    catch (SQLException e)
    { // turn SQLExceptions at this level into DataExceptions
      logger.error("DB error loading contact ID " + contactid + ": " + e.getMessage(),e);
      throw new DataException("Unable to look up contact info: " + e.getMessage(),e);
      
    } // end catch
    finally
    { // make sure we release the connection before we go
      env.releaseConnection(conn);

    } // end finally

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal functions
   *--------------------------------------------------------------------------------
   */

  private final void makeEmpty(int owner_uid, int owner_cid)
  {
    this.contactid = -1;
    this.given_name = null;
    this.family_name = null;
    this.middle_initial = ' ';
    this.prefix = null;
    this.suffix = null;
    this.company = null;
    this.addr1 = null;
    this.addr2 = null;
    this.locality = null;
    this.region = null;
    this.postal_code = null;
    this.country = null;
    this.phone = null;
    this.fax = null;
    this.mobile = null;
    this.email = null;
    this.photo_url = null;
    this.url = null;
    this.private_addr = false;
    this.private_phone = false;
    this.private_fax = false;
    this.private_email = false;
    this.owner_uid = owner_uid;
    this.owner_cid = owner_cid;
    this.last_update = null;

  } // end makeEmpty

  private final void loadData(Connection conn, int contactid) throws DataException
  {
    Statement stmt = null;

    try
    { // do a simple SELECT in contacts to look this up
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT * FROM contacts WHERE contactid = ");
      sql.append(contactid).append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (rs.next())
      { // do a mad orgy of saving off field values
	this.contactid = rs.getInt("contactid");
	given_name = rs.getString("given_name");
	family_name = rs.getString("family_name");
	String blort = rs.getString("middle_init");
	if ((blort!=null) && (blort.length()>0))
	  middle_initial = blort.charAt(0);
	else
	  middle_initial = ' ';
	prefix = rs.getString("prefix");
	suffix = rs.getString("suffix");
	company = rs.getString("company");
	addr1 = rs.getString("addr1");
	addr2 = rs.getString("addr2");
	locality = rs.getString("locality");
	region = rs.getString("region");
	postal_code = rs.getString("pcode");
	country = rs.getString("country");
	phone = rs.getString("phone");
	fax = rs.getString("fax");
	mobile = rs.getString("mobile");
	email = rs.getString("email");
	private_addr = rs.getBoolean("pvt_addr");
	private_phone = rs.getBoolean("pvt_phone");
	private_fax = rs.getBoolean("pvt_fax");
	private_email = rs.getBoolean("pvt_email");
	owner_uid = rs.getInt("owner_uid");
	owner_cid = rs.getInt("owner_sigid");
	photo_url = rs.getString("photo_url");
	url = rs.getString("url");
	last_update = SQLUtil.getFullDateTime(rs,"lastupdate");

      } // end if
      else
      { // contact was not found
	logger.error("contact ID " + contactid + " not found in database");
	throw new DataException("Contact was not found.");

      } // end else

    } // end try
    catch (SQLException e)
    { // map all SQLExceptions into DataExceptions
      logger.error("DB error loading contact ID " + contactid + ": " + e.getMessage(),e);
      throw new DataException("Unable to look up contact info: " + e.getMessage(),e);

    } // end catch
    finally
    { // shut down the statement to conserve resources
      SQLUtil.shutdown(stmt);

    } // end finally

  } // end loadData

  /*--------------------------------------------------------------------------------
   * Implementations from interface ContactInfo
   *--------------------------------------------------------------------------------
   */

  public int getContactID()
  {
    return contactid;

  } // end getContactID

  public String getGivenName()
  {
    return given_name;

  } // end getGivenName

  public void setGivenName(String name)
  {
    if ((name!=null) && (name.length()>64))
      given_name = name.substring(0,64);
    else
      given_name = name;
    is_modified = true;

  } // end setGivenName

  public String getFamilyName()
  {
    return family_name;

  } // end getFamilyName

  public void setFamilyName(String name)
  {
    if ((name!=null) && (name.length()>64))
      family_name = name.substring(0,64);
    else
      family_name = name;
    is_modified = true;

  } // end setFamilyName

  public char getMiddleInitial()
  {
    return middle_initial;

  } // end getMiddleInitial

  public void setMiddleInitial(char in)
  {
    middle_initial = in;
    is_modified = true;

  } // end setMiddleInitial

  public String getNamePrefix()
  {
    return prefix;

  } // end getNamePrefix

  public void setNamePrefix(String p)
  {
    if ((p!=null) && (p.length()>8))
      prefix = p.substring(0,8);
    else
      prefix = p;
    is_modified = true;

  } // end setNamePrefix

  public String getNameSuffix()
  {
    return suffix;

  } // end getNameSuffix

  public void setNameSuffix(String s)
  {
    if ((s!=null) && (s.length()>16))
      suffix = s.substring(0,16);
    else
      suffix = s;
    is_modified = true;

  } // end setNameSuffix

  public String getCompany()
  {
    return company;

  } // end getCompany

  public void setCompany(String c)
  {
    if ((c!=null) && (c.length()>255))
      company = c.substring(0,255);
    else
      company = c;
    is_modified = true;

  } // end setCompany

  public String getAddressLine1()
  {
    return addr1;

  } // end getAddressLine1

  public void setAddressLine1(String addr)
  {
    if ((addr!=null) && (addr.length()>255))
      addr1 = addr.substring(0,255);
    else
      addr1 = addr;
    is_modified = true;

  } // end setAddressLine1

  public String getAddressLine2()
  {
    return addr2;

  } // end getAddressLine2

  public void setAddressLine2(String addr)
  {
    if ((addr!=null) && (addr.length()>255))
      addr2 = addr.substring(0,255);
    else
      addr2 = addr;
    is_modified = true;

  } // end setAddressLine2

  public String getLocality()
  {
    return locality;

  } // end getLocality

  public void setLocality(String city)
  {
    if ((city!=null) && (city.length()>64))
      locality = city.substring(0,64);
    else
      locality = city;
    is_modified = true;

  } // end setLocality

  public String getRegion()
  {
    return region;

  } // end getRegion

  public void setRegion(String state)
  {
    if ((state!=null) && (state.length()>64))
      region = state.substring(0,64);
    else
      region = state;
    is_modified = true;

  } // end setRegion

  public String getPostalCode()
  {
    return postal_code;

  } // end getPostalCode

  public void setPostalCode(String zip)
  {
    if ((zip!=null) && (zip.length()>16))
      postal_code = zip.substring(0,16);
    else
      postal_code = zip;
    is_modified = true;

  } // end setPostalCode

  public String getCountry()
  {
    return country;

  } // end getCountry

  public void setCountry(String c)
  {
    if ((c!=null) && (c.length()>2))
      country = c.substring(0,2);
    else
      country = c;
    is_modified = true;

  } // end setCountry

  public String getPhone()
  {
    return phone;

  } // end getPhone

  public void setPhone(String num)
  {
    if ((num!=null) && (num.length()>32))
      phone = num.substring(0,32);
    else
      phone = num;
    is_modified = true;

  } // end setPhone

  public String getFax()
  {
    return fax;

  } // end getFax

  public void setFax(String num)
  {
    if ((num!=null) && (num.length()>32))
      fax = num.substring(0,32);
    else
      fax = num;
    is_modified = true;

  } // end setFax

  public String getMobile()
  {
    return mobile;

  } // end getMobile

  public void setMobile(String num)
  {
    if ((num!=null) && (num.length()>32))
      mobile = num.substring(0,32);
    else
      mobile = num;
    is_modified = true;

  } // end setMobile

  public String getEmail()
  {
    return email;

  } // end getEmail

  public void setEmail(String addr)
  {
    if ((addr!=null) && (addr.length()>255))
      email = addr.substring(0,255);
    else
      email = addr;
    is_modified = true;

  } // end setEmail

  public String getPhotoURL()
  {
    return photo_url;

  } // end getPhotoURL

  public void setPhotoURL(String addr)
  {
    if (!can_set_photo)
      return;
    if ((addr!=null) && (addr.length()>255))
      photo_url = addr.substring(0,255);
    else
      photo_url = addr;
    image_hook = null;
    is_modified = true;

  } // end setPhotoURL

  public void setPhotoData(String prefix, String mimetype, int length, InputStream data)
  {
    if (!can_set_photo)
      return;
    if ((photo_url!=null) && (photo_url.startsWith(prefix)))
    { // extract the image ID and create an image hook object
      int img_id = Integer.parseInt(photo_url.substring(prefix.length()));
      image_hook = new ExistingImageHook(img_id,mimetype,length,data);

    } // end if
    else
    { // figure out how to create the image hook object
      if (owner_cid>=0)
	image_hook = new NewImageHook(prefix,ImageStore.TYPE_COMMUNITY_LOGO,owner_cid,mimetype,length,data);
      else
	image_hook = new NewImageHook(prefix,ImageStore.TYPE_USER_PHOTO,owner_uid,mimetype,length,data);

    } // end else

    is_modified = true;

  } // end setPhotoData

  public String getURL()
  {
    return url;

  } // end getURL

  public void setURL(String addr)
  {
    if ((addr!=null) && (addr.length()>255))
      url = addr.substring(0,255);
    else
      url = addr;
    is_modified = true;

  } // end setURL

  public boolean getPrivateAddress()
  {
    return private_addr;

  } // end getPrivateAddress

  public void setPrivateAddress(boolean flag)
  {
    private_addr = flag;
    is_modified = true;

  } // end setPrivateAddress

  public boolean getPrivatePhone()
  {
    return private_phone;

  } // end getPrivatePhone

  public void setPrivatePhone(boolean flag)
  {
    private_phone = flag;
    is_modified = true;

  } // end setPrivatePhone

  public boolean getPrivateFax()
  {
    return private_fax;

  } // end getPrivateFax

  public void setPrivateFax(boolean flag)
  {
    private_fax = flag;
    is_modified = true;

  } // end setPrivateFax

  public boolean getPrivateEmail()
  {
    return private_email;

  } // end getPrivateEmail

  public void setPrivateEmail(boolean flag)
  {
    private_email = flag;
    is_modified = true;

  } // end setPrivateEmail

  public int getOwnerUID()
  {
    return owner_uid;

  } // end getOwnerUID

  public int getOwnerCommunityID()
  {
    return owner_cid;

  } // end getOwnerCommunityID

  public java.util.Date getLastUpdate()
  {
    return last_update;

  } // end getLastUpdate

  public boolean getModified()
  {
    return is_modified;

  } // end getModified

  public void importVCard(VCard vc)
  {
    // Import the easy fields first.
    if (vc.getGivenName()!=null)
      given_name = vc.getGivenName();
    if (vc.getFamilyName()!=null)
      family_name = vc.getFamilyName();
    if (vc.getMiddleName()!=null)
    { // modify the middle initial
      String tmp = vc.getMiddleName();
      middle_initial = (StringUtil.isStringEmpty(tmp) ? ' ' : tmp.charAt(0));

    } // end if

    if (vc.getPrefix()!=null)
      prefix = vc.getPrefix();
    if (vc.getSuffix()!=null)
      suffix = vc.getSuffix();
    if (vc.getOrganizationName()!=null)
      company = vc.getOrganizationName();
    if (vc.getURL()!=null)
    url = vc.getURL();

    // Look for an address.
    Boolean hide = null;
    VCardAddress addr = vc.getPreferredAddress();
    if (addr!=null)
    { // always hide the address if it's a home one
      if (addr.isHomeAddress())
	hide = Boolean.TRUE;

    } // end if
    if (addr==null)
      addr = vc.getWorkAddress();
    if (addr==null)
    { // get home address (and hide it)
      addr = vc.getHomeAddress();
      if (addr!=null)
	hide = Boolean.TRUE;

    } // end if
    if (addr==null)
    { // get any other address (and hide it)
      addr = vc.getAnyAddress();
      if ((addr!=null) && addr.isHomeAddress())
	hide = Boolean.TRUE;

    } // end if

    if (addr!=null)
    { // fill in the address values here
      if (addr.getPOBox()!=null)
      { // we have a PO Box...
	if (addr.getExtension()!=null)
	{ // and we have an extension too!
	  if (addr.getStreet()!=null)
	  { // Address line 1 is PO Box + extension, line 2 is street
	    addr1 = "PO Box " + addr.getPOBox() + ", " + addr.getExtension();
	    addr2 = addr.getStreet();

	  } // end if
	  else
	  { // Address line 1 is PO box, line 2 is extension
	    addr1 = "PO Box " + addr.getPOBox();
	    addr2 = addr.getExtension();
	    
	  } // end else

	} // end if
	else
	{ // Address line 1 is PO Box, line 2 is street
	  addr1 = "PO Box " + addr.getPOBox();
	  addr2 = addr.getStreet();

	} // end else

      } // end if
      else
      { // PO Box not present
	if (addr.getExtension()!=null)
	{ // Address line 1 is extension, line 2 is street
	  addr1 = addr.getExtension();
	  addr2 = addr.getStreet();

	} // end if
	else
	{ // Address line 1 is street, line 2 is null
	  addr1 = addr.getStreet();
	  addr2 = null;

	} // end else

      } // end else

      // Set the other fields.
      if (addr.getLocality()!=null)
	locality = addr.getLocality();
      if (addr.getRegion()!=null)
	region = addr.getRegion();
      if (addr.getPostalCode()!=null)
	postal_code = addr.getPostalCode();
      if ((addr.getCountry()!=null) && (addr.getCountry().length()==2))
	country = addr.getCountry().toUpperCase();

      // Set hide flag if appropriate.
      if (hide!=null)
	private_addr = hide.booleanValue();

    } // end if (loading address)

    // Figure out which phone number to put in.
    hide = null;
    VCardPhone p = vc.getPreferredPhone();
    if (p!=null)
    { // hide the phone if it's a home phone
      if (p.isHomePhone())
	hide = Boolean.TRUE;

    } // end if
    if (p==null)
      p = vc.getWorkPhone();
    if (p==null)
    { // try the home phone (and hide it)
      p = vc.getHomePhone();
      if (p!=null)
	hide = Boolean.TRUE;

    } // end if
    if (p==null)
    { // now try and get any voice phone
      p = vc.getVoicePhone();
      if ((p!=null) && p.isHomePhone())
	hide = Boolean.TRUE;

    } // end if
    if (p==null)
    { // now try and get any phone
      p = vc.getAnyPhone();
      if ((p!=null) && (p.isHomePhone() || p.isCellPhone() || p.isPCSPhone()))
	hide = Boolean.TRUE;

    } // end if

    if (p!=null)
      phone = p.getNumber();

    // now get the cellphone
    p = vc.getCellPhone();
    if (p!=null)
      mobile = p.getNumber();

    if (hide!=null)  // save hide value
      private_phone = hide.booleanValue();

    // now get the fax number
    hide = null;
    p = vc.getPreferredFax();
    if (p!=null)
    { // hide the phone if it's a home phone
      if (p.isHomePhone())
	hide = Boolean.TRUE;

    } // end if
    if (p==null)
      p = vc.getWorkFax();
    if (p==null)
    { // try the home phone (and hide it)
      p = vc.getHomeFax();
      if (p!=null)
	hide = Boolean.TRUE;

    } // end if
    if (p==null)
    { // now try and get any fax
      p = vc.getAnyFax();
      if ((p!=null) && (p.isHomePhone()))
	hide = Boolean.TRUE;

    } // end if

    // save off the fax number
    if (p!=null)
      fax = p.getNumber();
    if (hide!=null)
      private_fax = hide.booleanValue();

    // Get the internet address we wish to use.
    hide = null;
    VCardEmail em = vc.getPreferredEmail();
    if (em!=null)
    { // make sure the email is an Internet address!
      if (!(em.isInternetEmail()))
	em = null;
      else if (em.isHomeEmail())
	hide = Boolean.TRUE;

    } // end if
    if (em==null)
    { // get an Internet email address somehow
      em = vc.getInternetEmail();
      if ((em!=null) && em.isHomeEmail())
	hide = Boolean.TRUE;

    } // end if

    // Set the E-mail address.
    if (em!=null)
      email = em.getAddress();
    if (hide!=null)
      private_email = hide.booleanValue();

    is_modified = true;  // we definitely modified things now!

  } // end importVCard

  public boolean canSetPhoto()
  {
    return can_set_photo;

  } // end canSetPhoto

  /*--------------------------------------------------------------------------------
   * Implementations from interface Stashable
   *--------------------------------------------------------------------------------
   */

  public int getStashableUID()
  {
    return getOwnerUID();

  } // end getStashableUID

  public void stash(Connection conn) throws DataException, SQLException
  {
    if (logger.isDebugEnabled())
      logger.debug("stashing contact ID " + contactid);
    java.util.Date update = null;
    Statement stmt = null;

    try
    { // start updating the contact in the database
      stmt = conn.createStatement();
      StringBuffer buf;

      if (image_hook!=null)
      { // call the image hook to store an image and get a new photo URL (where applicable)
	String new_photo_url = image_hook.doImage(conn);
	if (new_photo_url!=null)
	  photo_url = new_photo_url;
	image_hook = null;

      } // end if

      if (contactid>=0)
      { // this involves updating an existing record
	buf = new StringBuffer("UPDATE contacts SET given_name = ");
	buf.append(SQLUtil.encodeStringArg(given_name)).append(", family_name = ");
	buf.append(SQLUtil.encodeStringArg(family_name)).append(", middle_init = ");
	if (middle_initial==' ')
	  buf.append("NULL");
	else
	  buf.append('\'').append(middle_initial).append('\'');
	buf.append(", prefix = ").append(SQLUtil.encodeStringArg(prefix));
	buf.append(", suffix = ").append(SQLUtil.encodeStringArg(suffix));
	buf.append(", company = ").append(SQLUtil.encodeStringArg(company));
	buf.append(", addr1 = ").append(SQLUtil.encodeStringArg(addr1));
	buf.append(", addr2 = ").append(SQLUtil.encodeStringArg(addr2));
	buf.append(", locality = ").append(SQLUtil.encodeStringArg(locality));
	buf.append(", region = ").append(SQLUtil.encodeStringArg(region));
	buf.append(", pcode = ").append(SQLUtil.encodeStringArg(postal_code));
	buf.append(", country = ").append(SQLUtil.encodeStringArg(country));
	buf.append(", phone = ").append(SQLUtil.encodeStringArg(phone));
	buf.append(", fax = ").append(SQLUtil.encodeStringArg(fax));
	buf.append(", mobile = ").append(SQLUtil.encodeStringArg(mobile));
	buf.append(", email = ").append(SQLUtil.encodeStringArg(email));
	buf.append(", pvt_addr = ").append(private_addr ? '1' : '0');
	buf.append(", pvt_phone = ").append(private_phone ? '1' : '0');
	buf.append(", pvt_fax = ").append(private_fax ? '1' : '0');
	buf.append(", pvt_email = ").append(private_email ? '1' : '0');
	buf.append(", photo_url = ").append(SQLUtil.encodeStringArg(photo_url));
	buf.append(", url = ").append(SQLUtil.encodeStringArg(url)).append(", lastupdate = '");
	update = new java.util.Date();
	buf.append(SQLUtil.encodeDate(update)).append("' WHERE contactid = ").append(contactid).append(';');
      
	stmt.executeUpdate(buf.toString());

      } // end if
      else
      { // this involves creating a new record
	if (owner_cid>=0)
	  stmt.executeUpdate("LOCK TABLES contacts WRITE, sigs WRITE;");
	else
	  stmt.executeUpdate("LOCK TABLES contacts WRITE, users WRITE;");

	try
	{ // insert contact record data
	  buf = new StringBuffer("INSERT INTO contacts (given_name, family_name, middle_init, prefix, "
				 + "suffix, company, addr1, addr2, locality, region, pcode, country, "
				 + "phone, fax, mobile, email, pvt_addr, pvt_phone, pvt_fax, pvt_email, "
				 + "owner_uid, owner_sigid, photo_url, url, lastupdate) VALUES (");
	  buf.append(SQLUtil.encodeStringArg(given_name)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(family_name)).append(", ");
	  if (middle_initial==' ')
	    buf.append("NULL, ");
	  else
	    buf.append('\'').append(middle_initial).append("', ");
	  buf.append(SQLUtil.encodeStringArg(prefix)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(suffix)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(company)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(addr1)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(addr2)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(locality)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(region)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(postal_code)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(country)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(phone)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(fax)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(mobile)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(email)).append(", ");
	  buf.append(private_addr ? '1' : '0').append(", ");
	  buf.append(private_phone ? '1' : '0').append(", ");
	  buf.append(private_fax ? '1' : '0').append(", ");
	  buf.append(private_email ? '1' : '0').append(", ");
	  buf.append(owner_uid).append(", ").append(owner_cid).append(", ");
	  buf.append(SQLUtil.encodeStringArg(photo_url)).append(", ");
	  buf.append(SQLUtil.encodeStringArg(url)).append(", '");
	  update = new java.util.Date();
	  buf.append(SQLUtil.encodeDate(update)).append("');");

	  stmt.executeUpdate(buf.toString());
	
	  // now read back the contact ID we just added
	  int new_contactid;
	  ResultSet rs = stmt.executeQuery("SELECT LAST_INSERT_ID();");
	  if (rs.next())
	  { // found the contact ID...
	    new_contactid = rs.getInt(1);
	    if (logger.isDebugEnabled())
	      logger.debug("created new contact ID " + new_contactid);

	  } // end if
	  else
	  { // error reading back the contact ID
	    logger.error("unable to read back contact ID");
	    throw new DataException("unable to read back new contact ID");

	  } // end else

	  // and patch the database table so we know what our contact ID is
	  buf.setLength(0);
	  if (owner_cid>=0)
	  { // update the SIGS table
	    buf.append("UPDATE sigs SET contactid = ").append(new_contactid).append(" WHERE sigid = ");
	    buf.append(owner_cid).append(';');

	  } // end if
	  else
	  { // update the USERS table
	    buf.append("UPDATE users SET contactid = ").append(new_contactid).append(" WHERE uid = ");
	    buf.append(owner_uid).append(';');

	  } // end else

	  stmt.executeUpdate(buf.toString());

	  contactid = new_contactid;  // save link

	} // end try
	finally
	{ // make sure the tables get unlocked
	  SQLUtil.unlockTables(conn);
	
	} // end finally
	
      } // end else

    } // end try
    finally
    { // shut down the statement to conserve resources
      SQLUtil.shutdown(stmt);

    } // end finally
    
    last_update = update;  // save last update date
    is_modified = false;
    
  } // end stash

} // end class ContactInfoImpl
