/*
 * Copyright 2002-2011 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.springframework.security.config.doc

import groovy.xml.Namespace

/**
 * Parses the Spring Security Xsd Document
 *
 * @author Rob Winch
 */
class SpringSecurityXsdParser {
	private def rootElement

	private def xs = new Namespace("http://www.w3.org/2001/XMLSchema", 'xs')
	private def attrElmts = [] as Set
	private def elementNameToElement = [:] as Map

	/**
	 * Returns a map of the element name to the {@link Element}.
	 * @return
	 */
	Map<String,Element> parse() {
		elements(rootElement)
		elementNameToElement
	}

	/**
	 * Creates a Map of the name to an Element object of all the children of element.
	 *
	 * @param element
	 * @return
	 */
	private def elements(element) {
		def elementNameToElement = [:] as Map
		element.children().each { c->
			if(c.name() == 'element') {
			  def e = elmt(c)
			  elementNameToElement.put(e.name,e)
			} else {
			  elementNameToElement.putAll(elements(c))
			}
		}
		elementNameToElement
	}

	/**
	 * Any children that are attribute will be returned as an Attribute object.
	 * @param element
	 * @return a collection of Attribute objects that are children of element.
	 */
	private def attrs(element) {
		def r = []
		element.children().each { c->
			if(c.name() == 'attribute') {
				r.add(attr(c))
			}else if(c.name() == 'element') {
			}else {
				r.addAll(attrs(c))
			}
		}
		r
	}

	/**
	 * Any children will be searched for an attributeGroup, each of it's children will be returned as an Attribute
	 * @param element
	 * @return
	 */
	private def attrgrps(element) {
		def r = []
		element.children().each { c->
			if(c.name() == 'element') {
			}else if (c.name() == 'attributeGroup') {
			   if(c.attributes().get('name')) {
				   r.addAll(attrgrp(c))
			   } else {
				   def n = c.attributes().get('ref').split(':')[1]
				   def attrGrp = findNode(element,n)
				   r.addAll(attrgrp(attrGrp))
			   }
			} else {
			   r.addAll(attrgrps(c))
			}
		}
		r
	}

	private def findNode(c,name) {
		def root = c
		while(root.name() != 'schema') {
			root = root.parent()
		}
		def result = root.breadthFirst().find { child-> name == child.@name?.text() }
		assert result?.@name?.text() == name
		result
	}

	/**
	 * Processes an individual attributeGroup by obtaining all the attributes and then looking for more attributeGroup elements and prcessing them.
	 * @param e
	 * @return all the attributes for a specific attributeGroup and any child attributeGroups
	 */
	private def attrgrp(e) {
		def attrs = attrs(e)
		attrs.addAll(attrgrps(e))
		attrs
	}

	/**
	 * Obtains the description for a specific element
	 * @param element
	 * @return
	 */
	private def desc(element) {
		return element['annotation']['documentation']
	}

	/**
	 * Given an element creates an attribute from it.
	 * @param n
	 * @return
	 */
	private def attr(n) {
		new Attribute(desc: desc(n), name: n.@name.text())
	}

	/**
	 * Given an element creates an Element out of it by collecting all its attributes and child elements.
	 *
	 * @param n
	 * @return
	 */
	private def elmt(n) {
		def name = n.@ref.text()
		if(name) {
			name = name.split(':')[1]
			n = findNode(n,name)
		} else {
		   name = n.@name.text()
		}
		if(elementNameToElement.containsKey(name)) {
			return elementNameToElement.get(name)
		}
		attrElmts.add(name)
		def e = new Element()
		e.name = n.@name.text()
		e.desc = desc(n)
		e.childElmts = elements(n)
		e.attrs = attrs(n)
		e.attrs.addAll(attrgrps(n))
		e.attrs*.elmt = e
		e.childElmts.values()*.each { it.parentElmts.put(e.name,e) }

		def subGrpName = n.@substitutionGroup.text()
		if(subGrpName) {
			def subGrp = elmt(findNode(n,subGrpName.split(":")[1]))
			subGrp.subGrps.add(e)
		}

		elementNameToElement.put(name,e)
		e
	}
}
