//______________________________________________________________________________
//   |       |
//   | knipp |           Knipp Medien und Kommunikation GmbH
//    -------
//
//
//     Knipp RRI Toolkit
//
//
//     Copyright (C) 2005 by Knipp Medien und Kommunikation GmbH
//
//     This library is free software; you can redistribute it and/or
//     modify it under the terms of the GNU Lesser General Public License 
//     as published by the Free Software Foundation; either version 2.1 
//     of the License, or any later version.
// 
//     This library is distributed in the hope that it will be useful, 
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
//     GNU Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General
//     Public License along with this library; if not, write to the 
//     Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
//     Boston, MA 02111-1307 USA 
//
//
//     initial author:    Klaus Malorny
//
//     version:           1.4 (15127), 2011/06/03 14:52:49
//                        $Revision: 15119 $ 
//                        $Date: 2011-05-30 18:18:45 +0200 (Mon, 30 May 2011) $
//                        $Author: Klaus.Malorny $
//

package de.knipp.rritoolkit.message;

import java.util.Map;
import java.util.HashMap;
import java.util.Date;

import java.io.Serializable;

import org.w3c.dom.Element;
import org.w3c.dom.DOMException;

import de.knipp.rritoolkit.KVList;
import de.knipp.rritoolkit.ResponseParseException;

import static de.knipp.rritoolkit.XmlHelper.getFirstElement;

import static de.knipp.rritoolkit.Utils.parseDateTime;


/**
 *  base class for all messages
 *
 *  @version  $Revision: 15119 $
 *  @author   Klaus Malorny
 */

public abstract class Message implements Serializable
{
  /** the message ID */
  
  private String messageID;
  
  
  /** the type of the message */
  
  private Type type;
  
  
  /** the point in time the message was generated */
  
  private Date created;
  
  
  /** map from the XML element's local name to the type */
  
  private static final Map<String, Type> elementToTypeMap;
  
  
  /**
   *  static constructor
   */
  
  static
  {
    elementToTypeMap = new HashMap<String, Type> ();
    
    elementToTypeMap.put ("expire", Type.DomainExpire);
    elementToTypeMap.put ("expireWarning", Type.DomainExpireWarning);
    elementToTypeMap.put ("authInfoExpire", Type.AuthInfoExpire);
    elementToTypeMap.put ("authInfo2Notify", Type.AuthInfo2Notify);
    elementToTypeMap.put ("chprovAuthInfo", Type.ChangeProvAuthInfo);
    elementToTypeMap.put ("deleteLegal", Type.DeleteLegal);
    elementToTypeMap.put ("domainDelete", Type.DomainDelete);
    elementToTypeMap.put ("authInfo2Delete", Type.AuthInfo2Delete);
  }
  
  
  /**
   *  set the member variables of this base class
   *
   *  @param theID                     the message ID
   *  @param theType                   the type
   *  @param theCreationDate           the date
   */
  
  protected void setData (String theID, Type theType, Date theCreationDate)
  {
    messageID = theID;
    type = theType;
    created = theCreationDate;
  }
  
  
  
  /**
   *  factory for the various message types
   *
   *  @param type                        the type, may be {@code null}
   *                                     (which behaves as an unknown type)
   *  @return                            the instance
   *  @throws IllegalArgumentException   if the given type is unknown 
   *                                     or unsupported
   */
  
  protected static Message createMessage (Type type)
  {
    if (type == null) throw new IllegalArgumentException ("type is missing");
    
    switch (type)
    {
      case ChangeProvAuthInfo:
        return new ChangeProvAuthInfoMessage ();

      case DomainExpire:
        return new DomainExpireMessage ();
        
      case DomainExpireWarning:
        return new DomainExpireWarningMessage ();
        
      case AuthInfoExpire:
        return new AuthInfoExpireMessage ();

      case AuthInfo2Notify:
        return new AuthInfo2NotifyMessage ();

      case DeleteLegal:
        return new DeleteLegalMessage ();

      case DomainDelete:
        return new DomainDeleteMessage ();

      case AuthInfo2Delete:
        return new AuthInfo2DeleteMessage ();
        
      default:
        throw new IllegalArgumentException ("unknown/unsupported type " + type);
    }
  }
  
  

  
  /**
   *  create an instance from the given XML element
   *
   *  @param el                       the element
   *  @return                         the created object
   *  @throws DOMException            on DOM exceptions
   *  @throws ResponseParseException  if the data was not recognised
   */
  
  public static Message fromXml (Element el)
    throws DOMException, ResponseParseException
  {
    Element elMsg = getFirstElement (el);
    
    if (elMsg == null) 
      throw new ResponseParseException (
        "message specific child element expected", el);
    
    String localName = elMsg.getLocalName ();
    Type type = elementToTypeMap.get (localName);
    Message msg;
    
    try
    {
      msg = createMessage (type);
    
    } catch (IllegalArgumentException e)
    {
      throw new ResponseParseException (
        "unknown or unsupported message element", el, e);
    }
    
    String messageID = el.getAttribute ("msgid");
    
    if (messageID == null) 
      throw new ResponseParseException ("message ID (msgid) is missing", el);
    
    Date created = parseDateTime (el.getAttribute ("msgtime"));
    
    if (created == null)
      throw new ResponseParseException ("message time (msgtime) is missing", 
        el);
    
    msg.setData (messageID, type, created);
    msg.dataFromXml (elMsg);
    
    return msg;
  }
  
  
  /**
   *  setup the object's data from the given XML element.
   *
   *  @param el                       the element
   *  @throws DOMException            on DOM exceptions
   *  @throws ResponseParseException  if the data was not recognised
   */

  protected abstract void dataFromXml (Element el)
    throws DOMException, ResponseParseException;
  
  
  
  
  /**
   *  initializes the instance from the given key-value multi-list
   *
   *  @param list                     the list
   *  @return                         the created message
   *  @throws ResponseParseException  if the list does not contain the
   *                                  expected content
   */
  
  public static Message fromList (KVList list)
    throws ResponseParseException
  {
    String typeName = list.getFirst ("msgtype");
    
    if (typeName == null)
      throw new ResponseParseException ("message type is missing", 
        "msgtype", (String) null);
    
    Type type = Type.fromToken (typeName);
    
    if (type == null)
      throw new ResponseParseException ("unknown message type", 
        "msgtype", typeName);
    
    Message msg;
    
    try
    {
      msg = createMessage (type);
      
    } catch (IllegalArgumentException e)
    {
      throw new ResponseParseException ("unsupported message type", 
        "msgtype", typeName);
    }

    String messageID = list.getFirst ("msgid");
    
    if (messageID == null) 
      throw new ResponseParseException ("message ID (msgid) is missing", 
        "msgid", (String) null);
    
    Date created = parseDateTime (list.getFirst ("msgtime"));
    
    if (created == null)
      throw new ResponseParseException ("message time (msgtime) is missing", 
        "msgtime", (String) null);
  
    msg.setData (messageID, type, created);
    msg.dataFromList (list);
    return msg;
  }
  
  
  /**
   *  setups the object's data from the given list. Subclasses should
   *  override this method and call the superclass first.
   *
   *  @param list                     the list
   *  @throws ResponseParseException  if the list does not contain the
   *                                  expected content
   */  
  
  protected abstract void dataFromList (KVList list)
    throws ResponseParseException;
  
  
  
  //--- access methods ---------------------------------------------------------
  
  
  /**
   *  returns the message ID
   *
   *  @return                          the message ID
   */
  
  public String getMessageID ()
  {
    return messageID;
  }
  
  
  
  /**
   *  returns the type of the message
   *
   *  @return                          the type
   */
  
  public Type getType ()
  {
    return type;
  }
  
  
  
  /**
   *  returns the point in time at which the message was generated
   *
   *  @return                         the time
   */
  
  public Date getCreationDate ()
  {
    return created;
  }
  
  
  //--- other ------------------------------------------------------------------
  
  
  /**
   *  calls the suitable method of the given visitor
   *
   *  @param visitor               the visitor
   */
  
  public abstract void accept (MessageVisitor visitor);
  
  
  
  /**
   *  returns a string representation for debugging purposes
   *
   *  @return                      the string representation
   */

  @Override
  public String toString ()
  {
    StringBuilder sb = new StringBuilder ();
    sb.append (super.toString ());
    sb.append ("\n  type: ");
    sb.append (type);
    sb.append ("\n  message ID: ");
    sb.append (messageID);
    sb.append ("\n  message date: ");
    sb.append (created);
    sb.append ("\n");
    return sb.toString ();
  }
  
  
  
  
  /**
   *  enumeration for the different message types
   *
   *  @version  $Revision: 15119 $
   *  @author   Klaus Malorny
   */
  
  public static enum Type
  {
    /** notification that a provider change via AuthInfo has taken place */

    ChangeProvAuthInfo ("chprovAuthInfo"),


    /** notification that the given failed" domains have been deleted */

    DomainExpire ("expire"),


    /** notification that the given "failed" domains will be deleted
        in near future */

    DomainExpireWarning ("expireWarning"),


    /** notification that the AuthInfo 1 of the given domain has expired */

    AuthInfoExpire ("authInfoExpire"),


    /** notification that an AuthInfo 2 has been set for the given domain */

    AuthInfo2Notify ("authInfo2Notify"),


    /** notification that a domain has been deleted */

    @Deprecated
    DeleteLegal ("deleteLegal"),


    /** notification that a domain has been deleted */

    DomainDelete ("domainDelete"),


    /** notification that an AuthInfo 2 has been deleted */

    AuthInfo2Delete ("authInfo2Delete");
    


    /** the DENIC token */
    
    private String denicToken;
    
    
    /** map from the token to the type */
    
    private static final Map<String, Type> tokenMap;
    
    
    static
    {
      tokenMap = new HashMap<String, Type> ();
      
      for (Type t: values ()) 
        tokenMap.put (t.denicToken.toLowerCase (), t);
    }
    
    /**
     *  constructor
     *
     *  @param token               the token that is used in DENIC messages
     *                             for representing the level
     */
    
    private Type (String token)
    {
      denicToken = token;
    }
 
    
    /**
     *  return the DENIC token
     *
     *  @return                     the token
     */
    
    public String getDenicToken ()
    {
      return denicToken;
    }
    
    
    
    /**
     *  return the level for the given token
     *
     *  @param denicToken           the token
     *  @return                     the level, or {@code null} if the
     *                              token is not found
     */
    
    public static Type fromToken (String denicToken)
    {
      return tokenMap.get (denicToken.toLowerCase ());
    }
  }

}