//______________________________________________________________________________
//   |       |
//   | 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: 14936 $
//                        $Date: 2010-11-05 15:57:40 +0100 (Fri, 05 Nov 2010) $
//                        $Author: Klaus.Malorny $
//

package de.knipp.rritoolkit.dns;

import java.util.StringTokenizer;

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

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

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

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


/**
 *  class that stores the DNSKEY information.
 *
 *  @version  $Revision: 14936 $
 *  @author   Klaus Malorny
 */

public class DnsEntryDNSKEY extends DnsEntry
{
  /** the flags */

  private int flags;


  /** the protocol */

  private int protocol;


  /** the algorithm */

  private int algorithm;


  /** the key data (Base64 encoded) */

  private String keyDataBase64;


  /** the key data (binary) */

  private byte[] keyDataBinary;


  /**
   *  constructor
   *
   *  @param keyFlags                the flags
   *  @param proto                   the protocol
   *  @param alg                     the algorithm
   *  @param data                    the key data
   */

  public DnsEntryDNSKEY (int keyFlags, int proto, int alg, byte[] data)
  {
    flags = keyFlags;
    protocol = proto;
    algorithm = alg;
    keyDataBinary = data;
  }



  /**
   *  constructor
   *
   *  @param keyFlags                the flags
   *  @param proto                   the protocol
   *  @param alg                     the algorithm
   *  @param data                    the key data, must be a valid Base64 string
   */

  public DnsEntryDNSKEY (int keyFlags, int proto, int alg, String data)
  {
    flags = keyFlags;
    protocol = proto;
    algorithm = alg;
    keyDataBase64 = data;
  }


  /**
   *  constructor
   *
   *  @param data                     the value of the key-value pair
   *  @param owner                    the encoded domain name
   *  @throws ResponseParseException  on syntactical errors
   */

  protected DnsEntryDNSKEY (String owner, String data)
    throws ResponseParseException
  {
    StringTokenizer st = new StringTokenizer (data);

    if (!st.hasMoreTokens ())
      throw new ResponseParseException ("flags are missing", data);

    try
    {
      flags = Integer.parseInt (st.nextToken ());

    } catch (NumberFormatException e)
    {
      throw new ResponseParseException ("flags is not a valid number",
        data, e);
    }

    if (!st.hasMoreTokens ())
      throw new ResponseParseException ("protocol is missing", data);

    try
    {
      protocol = Integer.parseInt (st.nextToken ());

    } catch (NumberFormatException e)
    {
      throw new ResponseParseException ("protocol is not a valid number",
        data, e);
    }

    if (!st.hasMoreTokens ())
      throw new ResponseParseException ("algorithm is missing", data);

    try
    {
      algorithm = Integer.parseInt (st.nextToken ());

    } catch (NumberFormatException e)
    {
      throw new ResponseParseException ("algorithm is not a valid number",
        data, e);
    }

    if (!st.hasMoreTokens ())
      throw new ResponseParseException ("key data is missing", data);

    keyDataBase64 = st.nextToken ();

    setOwner (owner);
  }


  /**
   *  constructor
   *
   *  @param el                       the element
   *  @throws DOMException            on DOM exceptions
   *  @throws ResponseParseException  on wrong content
   */
  
  protected DnsEntryDNSKEY (Element el)
    throws DOMException, ResponseParseException
  {
    String owner = getFirstElementText (el, NS_DNS_ENTRY, "owner");
    
    if (owner == null)
      throw new ResponseParseException ("missing <owner> element", el);
    
    Element rdata = getFirstElement (el, NS_DNS_ENTRY, "rdata");
    
    if (rdata == null)
      throw new ResponseParseException ("missing <rdata> element", el);
    
    Element elTemp = getFirstElement (rdata, NS_DNS_ENTRY, "flags");
    
    if (elTemp == null)
      throw new ResponseParseException ("missing <flags> element", rdata);

    String temp = elTemp.getTextContent ().trim ();

    try
    {
      flags = Integer.parseInt (temp);

    } catch (NumberFormatException e)
    {
      throw new ResponseParseException (
        "<flags> does not contain a valid number", elTemp, e);
    }

    elTemp = getFirstElement (rdata, NS_DNS_ENTRY, "protocol");

    if (elTemp == null)
      throw new ResponseParseException ("missing <protocol> element", rdata);

    temp = elTemp.getTextContent ().trim ();

    try
    {
      protocol = Integer.parseInt (temp);

    } catch (NumberFormatException e)
    {
      throw new ResponseParseException (
        "<protocol> does not contain a valid number", elTemp, e);
    }

    elTemp = getFirstElement (rdata, NS_DNS_ENTRY, "algorithm");

    if (elTemp == null)
      throw new ResponseParseException ("missing <algorithm> element", rdata);

    temp = elTemp.getTextContent ().trim ();

    try
    {
      algorithm = Integer.parseInt (temp);

    } catch (NumberFormatException e)
    {
      throw new ResponseParseException (
        "<algorithm> does not contain a valid number", elTemp, e);
    }

    elTemp = getFirstElement (rdata, NS_DNS_ENTRY, "publicKey");

    if (elTemp == null)
      throw new ResponseParseException ("missing <publicKey>", rdata);

    keyDataBase64 = elTemp.getTextContent ().trim ();
  }



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

  @Override
  public void toList (KVList list)
    throws EncodeException
  {
    StringBuilder sb = new StringBuilder ();
    sb.append (flags);
    sb.append (' ');
    sb.append (protocol);
    sb.append (' ');
    sb.append (algorithm);
    sb.append (' ');
    sb.append (getKeyDataBase64 ());

    list.add ("dnskey", sb.toString ());
  }

  /**
   *  convert 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
   */

  @Override
  public void toXml (Node node, String encodedDomainName)
    throws DOMException, EncodeException
  {
    XmlBuilder xb = new XmlBuilder (node);

    xb.es (NS_DNS_ENTRY, PREFIX_DNS_ENTRY, "dnsentry");

    Node current = xb.getCurrent ();
    String prefix = current.getPrefix ();

    xb.attr (NS_XSI, PREFIX_XSI, "type",
      prefix != null && prefix.length () > 0 ? prefix + ":DNSKEY" : "DNSKEY");

    xb.elt (NS_DNS_ENTRY, PREFIX_DNS_ENTRY, "owner", getOwner () == null ?
      encodedDomainName : getOwner ());

    xb.es (NS_DNS_ENTRY, PREFIX_DNS_ENTRY, "rdata");
    xb.elt (NS_DNS_ENTRY, PREFIX_DNS_ENTRY, "flags", flags);
    xb.elt (NS_DNS_ENTRY, PREFIX_DNS_ENTRY, "protocol", protocol);
    xb.elt (NS_DNS_ENTRY, PREFIX_DNS_ENTRY, "algorithm", algorithm);
    xb.elt (NS_DNS_ENTRY, PREFIX_DNS_ENTRY, "publicKey", getKeyDataBase64 ());
    xb.ee ().ee ();
  }




  /**
   *  accept the given visitor
   *
   *  @param visitor                  the visitor
   */

  @Override
  public void accept (DnsEntryVisitor visitor)
  {
    visitor.visit (this);
  }





  /**
   *  create a clone of this instance.
   *
   *  @return                         the clone
   */

  @Override
  public DnsEntryDNSKEY clone ()
  {
    DnsEntryDNSKEY copy = (DnsEntryDNSKEY) super.clone ();
    return copy;
  }



  /**
   *  create a string representation (for debugging purposes)
   *
   *  @return                    the string representation
   */

  @Override
  public String toString ()
  {
    return getOwner () + " DNSKEY " + flags + " " + protocol + " " +
      algorithm + " " + getKeyDataBase64 ();
  }



  //--- access methods ---------------------------------------------------------


  /**
   *  return the flags
   *
   *  @return               the flags
   */

  public int getFlags ()
  {
    return flags;
  }


  /**
   *  return the protocol
   *
   *  @return               the protocol
   */

  public int getProtocol ()
  {
    return protocol;
  }


  /**
   *  return the algorithm
   *
   *  @return               the algorithm
   */

  public int getAlgorithm ()
  {
    return algorithm;
  }


  /**
   *  return the key data as a Base64 string
   *
   *  @return               the key data, {@code null} if not set
   */

  public String getKeyDataBase64 ()
  {
    if (keyDataBase64 != null) return keyDataBase64;
    if (keyDataBinary == null) return null;

    keyDataBase64 = Utils.toBase64Binary (keyDataBinary);
    return keyDataBase64;
  }


  /**
   *  return the key data in binary representation
   *
   *  @return                             the key data, {@code null} if not set
   *  @throws IllegalArgumentException    if the internally stored Base64
   *                                      is invalid
   */

  public byte[] getKeyDataBinary ()
    throws IllegalArgumentException
  {
    if (keyDataBinary != null) return keyDataBinary;
    if (keyDataBase64 == null) return null;

    keyDataBinary = Utils.fromBase64Binary (keyDataBase64);
    return keyDataBinary;
  }



}

