package com.limegroup.gnutella.metadata;

import java.io.StringReader;
import java.io.IOException;
import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.limegroup.gnutella.xml.LimeXMLUtils;

/**
 * An encapsulation of the XML that describes Windows Media's
 * extended content encryption object.
 *
 * Construction will always succeed, but the object may be invalid.
 * Consult WRMXML.isValid() to see if the given XML was valid.
 */
public class WRMXML {
    
    public static final String PROTECTED = "licensed: ";
    
    // The XML should look something like:
    //<WRMHEADER>
    //    <DATA>
    //        <SECURITYVERSION>XXXX</SECURITYVERSION>
    //        <CID>XXXX</CID>
    //        <LAINFO>XXXX</LAINFO>
    //        <KID>XXXX</KID>
    //        <CHECKSUM>XXXX</CHECKSUM>
    //    </DATA>
    //    <SIGNATURE>
    //        <HASHALGORITHM type="XXXX"></HASHALGORITHM>
    //        <SIGNALGORITHM type="XXXX"></SIGNALGORITHM>
    //        <VALUE>XXXX</VALUE>
    //    </SIGNATURE>
    //</WRMHEADER> 
    
    protected String _securityversion, _cid, _lainfo, _kid, _checksum;
    protected String _hashalgorithm, _signalgorithm, _signatureValue;
    protected Node _documentNode;
    
    /**
     * Parses the given XML & constructs a WRMXML object out of it.
     */
    WRMXML(String xml) {
        parse(xml);
    }
    
    /**
     * Constructs a WRMXML object out of the given document.
     */
    WRMXML(Node documentNode) {
        parseDocument(documentNode);
    }
    
    /**
     * Determines is this WRMXML is well formed.
     * If it is not, no other methods are considered valid.
     */
    public boolean isValid() {
        return _documentNode != null &&
               _lainfo != null &&
               _hashalgorithm != null &&
               _signalgorithm != null &&
               _signatureValue != null;
    }
    
    public String getSecurityVersion() { return _securityversion; }
    public String getCID() { return _cid; }
    public String getLAInfo() { return _lainfo; }
    public String getKID() { return _kid; }
    public String getHashAlgorithm() { return _hashalgorithm; }
    public String getSignAlgorithm() { return _signalgorithm; }
    public String getSignatureValue() { return _signatureValue; }
    public String getChecksum() { return _checksum; }
    
    
    /** Parses the content encryption XML. */
    protected void parse(String xml) {
        DOMParser parser = new DOMParser();
        InputSource is = new InputSource(new StringReader(xml));
        try {
            parser.parse(is);
        } catch (IOException ioe) {
            return;
        } catch (SAXException saxe) {
            return;
        }
        
        parseDocument(parser.getDocument().getDocumentElement());
    }
    
    /**
     * Parses through the given document node, handing each child
     * node to parseNode.
     */
    protected void parseDocument(Node node) {
        _documentNode = node;
        if(!_documentNode.getNodeName().equals("WRMHEADER"))
            return;
        
        NodeList children = _documentNode.getChildNodes();
        for(int i = 0; i < children.getLength(); i++) {
            Node child = children.item(i);
            parseNode(child.getNodeName(), child);
        }
    }
    
    /**
     * Parses a node.
     * 'nodeName' is the parent node's name.
     * All child elements of this node are sent to parseChild, and all
     * attributes are parsed via parseAttributes.
     */
    protected void parseNode(String nodeName, Node data) {
        NodeList children = data.getChildNodes();
        for(int i = 0; i < children.getLength(); i++) {
            Node child = children.item(i);
            parseAttributes(nodeName, child);
                        
            String name = child.getNodeName();            
            String value = LimeXMLUtils.getTextContent(child);
            if(value == null)
                continue;
            value = value.trim();
            if(value.equals(""))
                continue;
                
            parseChild(nodeName, name, null, value);
        }
    }
    
    /**
     * Parses the attributes of a given node.
     * 'parentNodeName' is the parent node of this child, and child is the node
     * which the attributes are part of.
     * Attributes are sent to parseChild for parsing.
     */
    protected void parseAttributes(String parentNodeName, Node child) {
        NamedNodeMap nnm = child.getAttributes();
        String name = child.getNodeName();
        for(int i = 0; i < nnm.getLength(); i++) {
            Node attribute = nnm.item(i);
            String attrName = attribute.getNodeName();
            String attrValue = attribute.getNodeValue();
            if(attrValue == null)
                continue;
            attrValue = attrValue.trim();
            if(attrValue.equals(""))
                continue;
            parseChild(parentNodeName, name, attrName, attrValue);
        }
    }
    
    
    /**
     * Parses a child of the data node.
     * @param nodeName the parent node's name
     * @param name the name of this node
     * @param attribute the attribute's name, or null if not an attribute.
     * @param value the value of the node's text content (or the attribute)
     */
    protected void parseChild(String nodeName, String name, String attribute, String value) {
        if(nodeName.equals("DATA")) {
            if(attribute != null)
                return;            
            if(name.equals("SECURITYVERSION"))
                _securityversion = value;
            else if(name.equals("CID"))
                _cid = value;
            else if(name.equals("LAINFO"))
                _lainfo = value;
            else if(name.equals("KID"))
                _kid = value;
            else if(name.equals("CHECKSUM"))
                _checksum = value;
        } else if(nodeName.equals("SIGNATURE")) {
            if(name.equals("HASHALGORITHM") && "type".equals(attribute))
                _hashalgorithm = value;
            else if(name.equals("SIGNALGORITHM") && "type".equals(attribute))
                _signalgorithm = value;
            else if(name.equals("VALUE") && attribute == null)
                _signatureValue = value;
        }
    }
    
}