//______________________________________________________________________________
//   |       |
//   | 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.3 (15112), 2011/05/27 16:00:14
//                        $Revision: 15112 $ 
//                        $Date: 2011-05-27 15:48:22 +0200 (Fri, 27 May 2011) $
//                        $Author: Klaus.Malorny $
//

package de.knipp.rritoolkit.dns;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import java.util.regex.Pattern;
import java.util.regex.Matcher;

import java.io.Serializable;

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

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

import static de.knipp.rritoolkit.DenicConstants.NS_DNS_ENTRY;
import static de.knipp.rritoolkit.DenicConstants.NS_XSI;




/**
 *  base class for all name server entries
 *
 *  @version  $Revision: 15112 $
 *  @author   Klaus Malorny
 */

public abstract class DnsEntry implements Cloneable, Serializable
{
  /** the owner name */
  
  private String owner;
  
  
  /** the pattern to match the entry */
  
  private static final Pattern parsePattern = Pattern.compile (
    "(\\S+)\\s+(IN\\s+)(\\S+)\\s+(.+)", Pattern.CASE_INSENSITIVE);



  /**
   *  create an name server entry
   *
   *  @param value                    the value
   *  @return                         the created object
   *  @throws ResponseParseException  if the data was not recognized
   */

  private static DnsEntry createNSEntry (String value)
    throws ResponseParseException
  {
    Matcher m = parsePattern.matcher (value);

    if (!m.matches ())
      throw new ResponseParseException ("unrecognized format of DNS entry",
        "nsentry", value);

    String owner = m.group (1);
    String type = m.group (3).toLowerCase ();
    String data = m.group (4);

    DnsEntry entry;

    if (type.equals ("a"))
    {
      entry = new DnsEntryA (owner, data);

    } else if (type.equals ("aaaa"))
    {
      entry = new DnsEntryAAAA (owner, data);

    } else if (type.equals ("mx"))
    {
      entry = new DnsEntryMX (owner, data);

    } else
    {
      throw new ResponseParseException ("unknown record type \"" + type + "\"",
        "nsentry", value);
    }

    return entry;
  }




  /**
   *  create an instances from the given key-value list
   *
   *  @param list                     the list
   *  @param encodedDomainName        the encoded domain name from the domain
   *                                  info data
   *  @return                         the created objects
   *  @throws ResponseParseException  if the data was not recognized
   */
  
  public static List<DnsEntry> allFromList (KVList list,
    String encodedDomainName)
    throws ResponseParseException
  {
    List<DnsEntry> result = new ArrayList<DnsEntry> ();

    for (String line: list.getList ("nsentry"))
      result.add (createNSEntry (line));

    for (String line: list.getList ("dnskey"))
      result.add (new DnsEntryDNSKEY (encodedDomainName, line));

    Map<String, List<String>> nameservers =
      new LinkedHashMap<String, List<String>> ();

    for (String line: list.getList ("nserver"))
    {
      StringTokenizer st = new StringTokenizer (line);

      if (!st.hasMoreTokens ())
        throw new ResponseParseException ("name server name missing",
          "nserver", line);

      String name = st.nextToken ();
      List<String> ips = nameservers.get (name);

      if (ips == null)
      {
        ips = new ArrayList<String> ();
        nameservers.put (name, ips);
      }

      if (st.hasMoreTokens ())
        ips.add (st.nextToken ());
    }

    for (Map.Entry<String, List<String>> entry: nameservers.entrySet ())
      result.add (new DnsEntryNS (encodedDomainName,
        entry.getKey (), entry.getValue ()));

    return result;
  }
  
 
  
  
  /**
   *  creates 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 recognized
   */
  
  public static DnsEntry fromXml (Element el)
    throws DOMException, ResponseParseException
  {
    // determine type. As we do not make validation mandatory, we cannot
    // rely on getSchemaTypeInfo to return a valid type
   
    // TODO: Check that the method really does not return the information
    
    String type = el.getAttributeNS (NS_XSI, "type");
    String typeLocal;
    String typePrefix;
    int pos = type.indexOf (':');
    
    if (pos >= 0)
    {
      typeLocal = type.substring (pos + 1);
      typePrefix = type.substring (0, pos);
      
    } else
    {
      typeLocal = type;
      typePrefix = "";
    }
    
    String typeNS = el.lookupNamespaceURI (typePrefix);

    DnsEntry entry = null;
    
    if (NS_DNS_ENTRY.equals (typeNS))
    {
      if (typeLocal.equals ("NS"))
      {
        entry = new DnsEntryNS (el);
        
      } else if (typeLocal.equals ("A"))
      {
        entry = new DnsEntryA (el);

      } else if (typeLocal.equals ("AAAA"))
      {
        entry = new DnsEntryAAAA (el);

      } else if (typeLocal.equals ("MX"))
      {
        entry = new DnsEntryMX (el);
      
      } else if (typeLocal.equals ("DNSKEY"))
      {
        entry = new DnsEntryDNSKEY (el);
      }
    }
    
    if (entry == null)
      throw new ResponseParseException (
        "failed to determine type of element", el);
    
    return entry;
  }
  

  
  /**
   *  converts the instance to an entry in the given list
   *
   *  @param list                     the list
   *  @throws EncodeException         if the encoding fails
   */

  public abstract void toList (KVList list)
    throws EncodeException;
  
  
  
  /**
   *  converts the instance to XML
   *
   *  @param node                     the node to append to
   *  @param encodedDomainName        the encoded domain name from the domain
   *                                  info data
   *  @throws DOMException            on DOM exceptions
   *  @throws EncodeException         if the encoding fails
   */
  
  public abstract void toXml (Node node, String encodedDomainName)
    throws DOMException, EncodeException;
  
  
  
  
  
  /**
   *  accepts the given visitor
   *
   *  @param visitor                  the visitor
   */
   
  public abstract void accept (DnsEntryVisitor visitor);
  
  
  
  
  /**
   *  creates a clone of this instance.
   *
   *  @return                         the clone
   */

  @Override
  public DnsEntry clone ()
  {
    try
    {
      return (DnsEntry) super.clone ();

    } catch (CloneNotSupportedException e)
    {
      throw new RuntimeException ("unexpected exception", e);
    }
  }





  /**
   *  test whether the given address is an IPv6 address
   *
   *  @param addr           the address
   *  @return               {@code true} if it is likely an IPv6 address
   */

  protected static boolean isIPv6 (String addr)
  {
    // it is simply tested whether a colon occurs in the string; for valid
    // addresses, this should be sufficient

    return addr.indexOf (':') >= 0;
  }


  
  //--- access methods ---------------------------------------------------------
  
  /**
   *  returns the owner
   *
   *  @return                         the owner
   */
  
  public String getOwner ()
  {
    return owner;
  }
  
  
  
  /**
   *  sets the owner
   *
   *  @param theOwner                 the owner
   */
  
  protected void setOwner (String theOwner)
  {
    owner = theOwner;
  }
}



