/*
 * #%L
 * =====================================================
 *   _____                _     ____  _   _       _   _
 *  |_   _|_ __ _   _ ___| |_  / __ \| | | | ___ | | | |
 *    | | | '__| | | / __| __|/ / _` | |_| |/ __|| |_| |
 *    | | | |  | |_| \__ \ |_| | (_| |  _  |\__ \|  _  |
 *    |_| |_|   \__,_|___/\__|\ \__,_|_| |_||___/|_| |_|
 *                             \____/
 * 
 * =====================================================
 * 
 * Hochschule Hannover
 * (University of Applied Sciences and Arts, Hannover)
 * Faculty IV, Dept. of Computer Science
 * Ricklinger Stadtweg 118, 30459 Hannover, Germany
 * 
 * Email: trust@f4-i.fh-hannover.de
 * Website: http://trust.f4.hs-hannover.de/
 * 
 * This file is part of irond, version 0.5.8, implemented by the Trust@HsH
 * research group at the Hochschule Hannover.
 * %%
 * Copyright (C) 2010 - 2016 Trust@HsH
 * %%
 * 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.
 * #L%
 */
package de.hshannover.f4.trust.iron.mapserver.binding;


import java.util.List;

import javax.xml.bind.Binder;
import javax.xml.bind.JAXB;

import org.trustedcomputinggroup._2010.ifmap._2.AccessRequestType;
import org.trustedcomputinggroup._2010.ifmap._2.DeleteType;
import org.trustedcomputinggroup._2010.ifmap._2.DeviceType;
import org.trustedcomputinggroup._2010.ifmap._2.IPAddressType;
import org.trustedcomputinggroup._2010.ifmap._2.IdentityType;
import org.trustedcomputinggroup._2010.ifmap._2.MACAddressType;
import org.trustedcomputinggroup._2010.ifmap._2.NotifyType;
import org.trustedcomputinggroup._2010.ifmap._2.SearchType;
import org.trustedcomputinggroup._2010.ifmap._2.UpdateType;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import de.hshannover.f4.trust.iron.mapserver.datamodel.identifiers.AccessRequest;
import de.hshannover.f4.trust.iron.mapserver.datamodel.identifiers.Device;
import de.hshannover.f4.trust.iron.mapserver.datamodel.identifiers.DeviceTypeEnum;
import de.hshannover.f4.trust.iron.mapserver.datamodel.identifiers.Identifier;
import de.hshannover.f4.trust.iron.mapserver.datamodel.identifiers.Identity;
import de.hshannover.f4.trust.iron.mapserver.datamodel.identifiers.IdentityTypeEnum;
import de.hshannover.f4.trust.iron.mapserver.datamodel.identifiers.IpAddress;
import de.hshannover.f4.trust.iron.mapserver.datamodel.identifiers.IpAddressTypeEnum;
import de.hshannover.f4.trust.iron.mapserver.datamodel.identifiers.MacAddress;
import de.hshannover.f4.trust.iron.mapserver.exceptions.InvalidIdentifierException;
import de.hshannover.f4.trust.iron.mapserver.exceptions.SystemErrorException;
import de.hshannover.f4.trust.iron.mapserver.exceptions.UnmarshalException;
import de.hshannover.f4.trust.iron.mapserver.utils.DomHelpers;
import de.hshannover.f4.trust.iron.mapserver.utils.NullCheck;

/**
 * This class contains methods which can be used to transform from autogenerated
 * Identifier types to datamodel Identifiers and back.
 *
 * FIXME:
 * No guarantee for correct transformation, there really should be some tests...
 *
 * @version 0.1
 * @author aw
 *
 * created: 10.02.10
 *
 * changes:
 *   12.02.10 aw - changed identifier transformation to "not" set the
 *		 administrative domain if it's empty
 *	       - Changing for jaxbri code
 *   14.02.10 aw - some cleanup and commenting, moded type enum transformations
 *		 into TypeTransformer class
 *
 */
class JaxbIdentifierHelper {

	/*
	 * Extract a datamodel Identifier from a SearchType object and return
	 * it.
	 *
	 * @param search
	 * @return
	 * @throws InvalidIdentifierException
	 */
	Identifier extractFromSearch(SearchType search, Binder<Node> binder)
			throws InvalidIdentifierException, UnmarshalException {
		NullCheck.check(search, "search null");
		Object o = null;

		if (search.getAccessRequest() != null) {
			o = search.getAccessRequest();
		} else if (search.getDevice() != null) {
			o = search.getDevice();
		} else if (search.getIdentity() != null) {
			o = search.getIdentity();
		} else if (search.getIpAddress() != null) {
			o = search.getIpAddress();
		} else if (search.getMacAddress() != null) {
			o = search.getMacAddress();
		} else {
			throw new UnmarshalException("identifier not set in search");
		}

		return transformJaxbObjectToIdentifier(o, binder);
	}

	/**
	 * Return an array of datamodel Identifiers given a UpdateType
	 *
	 * @param value
	 * @return
	 * @throws InvalidIdentifierException
	 * @throws UnmarshalException
	 */
	Identifier[] extractFromUpdate(UpdateType value, Binder<Node> binder) throws
											InvalidIdentifierException,
											UnmarshalException {
		NullCheck.check(value, "value null");
		List<Object> list = value.getAccessRequestOrIdentityOrIpAddress();
		return extractIdentifierArray(list, 2, binder);
	}

	/**
	 * Return an array of datamodel Identifiers given a DeleteType
	 *
	 * @param value
	 * @return
	 * @throws InvalidIdentifierException
	 * @throws UnmarshalException
	 */
	Identifier[] extractFromDelete(DeleteType value, Binder<Node> binder) throws
											InvalidIdentifierException,
											UnmarshalException {
		NullCheck.check(value, "value null");
		List<Object> list = value.getAccessRequestOrIdentityOrIpAddress();
		return extractIdentifierArray(list, 2, binder);
	}

	/**
	 * Return an array of datamodel Identifiers given a NotifyTYpe
	 *
	 * @param value
	 * @return
	 * @throws InvalidIdentifierException
	 * @throws UnmarshalException
	 */
	Identifier[] extractFromNotify(NotifyType value, Binder<Node> binder) throws
											InvalidIdentifierException,
											UnmarshalException {
		NullCheck.check(value, "value null");
		List<Object> list = value.getAccessRequestOrIdentityOrIpAddress();
		return extractIdentifierArray(list, 2, binder);
	}

	/**
	 * Helper for the last 3 methods
	 *
	 * @param olist
	 * @param max
	 * @return
	 * @throws UnmarshalException
	 * @throws InvalidIdentifierException
	 */
	private Identifier[] extractIdentifierArray(List<Object> olist, int max,
			Binder<Node> binder) throws UnmarshalException, InvalidIdentifierException {
		Identifier ret[];

		if (olist == null) {
			throw new UnmarshalException("no identifiers in publish");
		}

		if (olist.size() == 0 || olist.size() > max) {
			throw new UnmarshalException("bad number of identifiers in update");
		}

		ret = new Identifier[max];

		for (int i = 0; i < olist.size(); i++) {
			Object o = olist.get(i);
			if (o == null) {
				throw new UnmarshalException("invalid identifier");
			}

			ret[i] = transformJaxbObjectToIdentifier(o, binder);
		}

		return ret;
	}

	/**
	 * Helper method which converts from a given Object o into
	 * a datamodel Identifier.
	 *
	 * @param o
	 * @return
	 * @throws InvalidIdentifierException
	 */
	Identifier transformJaxbObjectToIdentifier(Object o, Binder<Node> binder)
			throws InvalidIdentifierException {

		Identifier ret = null;

		if (o instanceof AccessRequestType) {
			ret = transformAR((AccessRequestType)o);
		} else if (o instanceof DeviceType) {
			ret = transformDevice((DeviceType)o);
		} else if (o instanceof IPAddressType) {
			ret = transformIP((IPAddressType)o);
		} else if (o instanceof IdentityType) {
			ret = transformIdentity((IdentityType)o);
		} else if (o instanceof MACAddressType) {
			ret = transformMacAddress((MACAddressType)o);
		} else {
			throw new InvalidIdentifierException("unknown identifier");
		}

		Node n = binder.getXMLNode(o);

		if (n == null) {
			throw new SystemErrorException("Could not get XML Node");
		}

		if (!(n instanceof Element)) {
			throw new SystemErrorException("Identifier is not Element");
		}

		Document identDoc = DomHelpers.deepCopy((Element)n);

		if (identDoc == null) {
			throw new SystemErrorException("deep copy gave null");
		}

		ret.setXmlDocument(identDoc);

		return ret;
	}

	/**
	 * Transform a {@link Identifier} implementation to an {@link JAXB} generated
	 * Identifier type.
	 *
	 * @param identifier
	 * @return
	 */
	Object transformIdentifierToJaxbObject(Identifier identifier) {
		NullCheck.check(identifier, "identifier is null");

		if (identifier instanceof AccessRequest) {
			return transformAR((AccessRequest)identifier);
		} else if (identifier instanceof Device) {
			return transformDevice((Device)identifier);
		} else if (identifier instanceof Identity) {
			return transformIdentity((Identity)identifier);
		} else if (identifier instanceof IpAddress) {
			return transformIP((IpAddress)identifier);
		} else if (identifier instanceof MacAddress) {
			return transformMacAddress((MacAddress)identifier);
		} else {
			throw new SystemErrorException("Unknown Identifier class: " +
					identifier.getClass().getCanonicalName());
		}
	}

	/**
	 * From autogenerated IP to datamodel IP
	 *
	 * @return
	 * @throws `
	 */
	private IpAddress transformIP(IPAddressType iat)
			throws InvalidIdentifierException {
		NullCheck.check(iat, "iat null");
		IpAddressTypeEnum  ipt = transformIpAddressType(iat.getType());
		IpAddress ret = new IpAddress(iat.getValue(), iat.getAdministrativeDomain(), ipt);
		return ret;
	}

	/**
	 * From datamodel IP to autogenerated IP
	 *
	 * @param ip
	 * @return
	 */
	IPAddressType transformIP(IpAddress ip) {
		NullCheck.check(ip, "ip null");
		IPAddressType ret = null;
		if (ip != null) {
			ret = new IPAddressType();
			ret.setType(transformIpAddressType(ip.getIpAddressType()));
			ret.setValue(ip.getValue());

			/* we always have a administrative domain, but length may be 0 */
			if (ip.getAdministrativeDomain().length() == 0) {
				ret.setAdministrativeDomain(null);
			} else {
				ret.setAdministrativeDomain(ip.getAdministrativeDomain());
			}
		}
		return ret;
	}

	/**
	 * Transform from a IpAddressType string into IpAdressType enum
	 *
	 * @param ipt
	 * @return
	 * @throws InvalidIdentifierException
	 */
	private IpAddressTypeEnum transformIpAddressType(String ipt) throws InvalidIdentifierException {
		IpAddressTypeEnum ret = IpAddressTypeEnum.IPv4;
		if (ipt != null) {
			try {
			ret = IpAddressTypeEnum.valueOf(ipt);
			} catch (IllegalArgumentException e) {
				throw new InvalidIdentifierException("Invalid ip-address type"
						+ "(" + ipt + ")");
			}
		}
		return ret;
	}

	/**
	 * Convert a datamodel IpAddressType enum into an String
	 * containing the IpAddressType
	 *
	 * @param ipt
	 * @return
	 */
	private String transformIpAddressType(IpAddressTypeEnum ipt) {
		NullCheck.check(ipt, "ipt null");
		return ipt.toString();
	}

	/**
	 * From autogenerated Mac to datamodel Mac
	 *
	 * @param mt
	 * @return
	 * @throws InvalidIdentifierException
	 */
	private MacAddress transformMacAddress(MACAddressType mt)
										throws InvalidIdentifierException {
		NullCheck.check(mt, "mt null");
		return new MacAddress(mt.getValue(), mt.getAdministrativeDomain());
	}


	/**
	 * From datamodel Mac to autogenerated Mac
	 *
	 * @param mac
	 * @return
	 */
	MACAddressType transformMacAddress(MacAddress mac) {
		NullCheck.check(mac, "mac is null");
		MACAddressType ret = new MACAddressType();
		ret.setValue(mac.getValue());
		if (mac.getAdministrativeDomain().length() == 0) {
			ret.setAdministrativeDomain(null);
		} else {
			ret.setAdministrativeDomain(mac.getAdministrativeDomain());
		}
		return ret;
	}


	/**
	 * From autogenerated Identity to datamodel Identity
	 *
	 * @param it
	 * @return
	 * @throws InvalidIdentifierException
	 */
	private Identity transformIdentity(IdentityType it)
			throws InvalidIdentifierException {
		NullCheck.check(it, "it is null");
		return new Identity(it.getName(),
				it.getAdministrativeDomain(),
				it.getOtherTypeDefinition(),
				transformIdentityType(it.getType()));
	}


	/**
	 * From datamodel Identity to autogenerated Identity
	 *
	 * @param identity
	 * @return
	 */
	IdentityType transformIdentity(Identity identity) {
		NullCheck.check(identity, "identity is null");
		IdentityType ret = new IdentityType();
		String t = transformIdentityType(identity.getIdentityType());
		ret.setType(t);
		ret.setName(identity.getName());

		if (identity.getAdministrativeDomain().length() == 0) {
			ret.setAdministrativeDomain(null);
		} else {
			ret.setAdministrativeDomain(identity.getAdministrativeDomain());
		}
		if (identity.getIdentityType() == IdentityTypeEnum.other) {
			ret.setOtherTypeDefinition(identity.getOtherTypeDefinition());
		}
		return ret;
	}


	/**
	 * From autogenerated Device to datamodel Device
	 *
	 * @param dt
	 * @return
	 * @throws InvalidIdentifierException
	 */
	private Device transformDevice(DeviceType dt)
										throws InvalidIdentifierException {
		NullCheck.check(dt, "dt null");
		Device ret = null;
		if (dt.getAikName() != null) {
			ret = new Device(dt.getAikName(), DeviceTypeEnum.aikName);
		} else {
			ret = new Device(dt.getName(), DeviceTypeEnum.name);
		}
		return ret;
	}


	/**
	 * From datamodel Device to autogenerated Device
	 *
	 * @param dev
	 * @return
	 */
	DeviceType transformDevice(Device dev) {
		NullCheck.check(dev, "dev null");
		DeviceType ret = new DeviceType();
		if (dev.getDeviceType() == DeviceTypeEnum.aikName) {
			ret.setAikName(dev.getValue());
		} else {
			ret.setName(dev.getValue());
		}
		return ret;
	}


	/**
	 * From autogenerated Access Request to datamodel Access Request
	 *
	 * @param art
	 * @return
	 * @throws InvalidIdentifierException
	 */
	private AccessRequest transformAR(AccessRequestType art)
										throws InvalidIdentifierException {
		NullCheck.check(art, "art null");
		return new AccessRequest(art.getName(), art.getAdministrativeDomain());
	}


	/**
	 * From datamodel Access Request to autogenerated Access Request
	 *
	 * @param ar
	 * @return
	 */
	AccessRequestType transformAR(AccessRequest ar) {
		NullCheck.check(ar, "ar null");
		AccessRequestType ret = new AccessRequestType();
		ret.setName(ar.getName());
		if (ar.getAdministrativeDomain().length() == 0) {
			ret.setAdministrativeDomain(null);
		} else {
			ret.setAdministrativeDomain(ar.getAdministrativeDomain());
		}
		return ret;
	}




	/**
	 * Helper method to transform auto generated identity type strings to
	 * datamodel identity type enums
	 *
	 * Depends on the right order of the enum and string array!
	 *
	 * @param t
	 * @return
	 * @throws InvalidIdentifierException
	 */
	private IdentityTypeEnum transformIdentityType(String type)
									throws  InvalidIdentifierException {
		if (type != null) {
			for (IdentityTypeEnum cur : IdentityTypeEnum.values()) {
				if (cur.toString().equals(type)) {
					return cur;
				}
			}
		}
		// if we come here something failed, this can also happen if type is null
		throw new InvalidIdentifierException("invalid identity type");
	}


	/**
	 * Helper method to transform datamodel identity type enums to
	 * autogenerated identity type strings
	 *
	 * Depends on the right order of the enum and string array!
	 *
	 * @param type
	 * @return
	 */
	private String transformIdentityType(IdentityTypeEnum type) {
		NullCheck.check(type, "type null");
		return type.toString();
	}


}
