package org.jboss.resteasy.plugins.providers.atom;

import java.util.ArrayList;
import java.util.List;

import jakarta.ws.rs.core.MediaType;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAnyElement;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlMixed;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;

import org.jboss.resteasy.plugins.providers.jaxb.JAXBContextFinder;
import org.jboss.resteasy.plugins.providers.jaxb.JAXBXmlTypeProvider;
import org.w3c.dom.Element;

/**
 * <p>
 * Represents an atomTextConstruct element.
 * </p>
 * <p>
 * Per RFC4287:
 * </p>
 *
 * <pre>
 *  A Text construct contains human-readable text, usually in small
 *  quantities. The content of Text constructs is Language-Sensitive.
 *
 *  atomPlainTextConstruct =
 *     atomCommonAttributes,
 *     attribute type { "text" | "html" }?,
 *     text
 *
 *  atomXHTMLTextConstruct =
 *    atomCommonAttributes,
 *    attribute type { "xhtml" },
 *    xhtmlDiv
 *
 *  atomTextConstruct = atomPlainTextConstruct | atomXHTMLTextConstruct
 *
 * </pre>
 *
 * @author <a href="mailto:mail@johannes-beck.name">Johannes Beck</a>
 * @version $Revision: 1 $
 */
@XmlAccessorType(XmlAccessType.PROPERTY)
public class Text extends CommonAttributes {

    private String type;

    private MediaType mediaType;

    private String text;

    private Element element;

    private List<Object> value;

    private Object jaxbObject;

    protected JAXBContextFinder finder;

    public Text() {
    }

    public Text(final String text, final String type) {
        setText(text);
        setRawType(type);
    }

    public Text(final String text) {
        setText(text);
    }

    protected void setFinder(JAXBContextFinder finder) {
        this.finder = finder;
    }

    @XmlAnyElement
    @XmlMixed
    public List<Object> getValue() {
        return value;
    }

    public void setValue(List<Object> value) {
        this.value = value;
    }

    /**
     * Mime type.
     *
     * @return media type
     */
    @XmlTransient
    public MediaType getType() {
        if (mediaType == null) {
            if (type.equals("html"))
                mediaType = MediaType.TEXT_HTML_TYPE;
            else if (type.equals("text"))
                mediaType = MediaType.TEXT_PLAIN_TYPE;
            else if (type.equals("xhtml"))
                mediaType = MediaType.APPLICATION_XHTML_XML_TYPE;
            else
                mediaType = MediaType.valueOf(type);
        }
        return mediaType;
    }

    public void setType(MediaType type) {
        mediaType = type;
        if (type.equals(MediaType.TEXT_PLAIN_TYPE))
            this.type = "text";
        else if (type.equals(MediaType.TEXT_HTML_TYPE))
            this.type = "html";
        else if (type.equals(MediaType.APPLICATION_XHTML_XML_TYPE))
            this.type = "xhtml";
        else
            this.type = type.toString();
    }

    @XmlAttribute(name = "type")
    public String getRawType() {
        return type;
    }

    public void setRawType(String type) {
        this.type = type;
    }

    /**
     * If content is text, return it as a String. Otherwise, if content is not text this will return null.
     *
     * @return text
     */
    @XmlTransient
    public String getText() {
        if (value == null)
            return null;
        if (value.size() == 0)
            return null;
        if (text != null)
            return text;
        StringBuffer buf = new StringBuffer();
        for (Object obj : value) {
            if (obj instanceof String)
                buf.append(obj.toString());
        }
        text = buf.toString();
        return text;
    }

    /**
     * Set content as text.
     *
     * @param text text
     */
    public void setText(String text) {
        if (value == null)
            value = new ArrayList<Object>();
        if (this.text != null && value != null)
            value.clear();
        this.text = text;
        value.add(text);
    }

    /**
     * Get content as an XML Element if the content is XML. Otherwise, this will just return null.
     *
     * @return {@link Element}
     */
    @XmlTransient
    public Element getElement() {
        if (value == null)
            return null;
        if (element != null)
            return element;
        for (Object obj : value) {
            if (obj instanceof Element) {
                element = (Element) obj;
                return element;
            }
        }
        return null;
    }

    /**
     * Set the content to an XML Element.
     *
     * @param element {@link Element}
     */
    public void setElement(Element element) {
        if (value == null)
            value = new ArrayList<Object>();
        if (this.element != null && value != null)
            value.clear();
        this.element = element;
        value.add(element);

    }

    /**
     * Extract the content as the provided JAXB annotated type.
     * <p>
     * This method will use a cached JAXBContext used by the Resteasy JAXB providers
     * or, if those are not existent, it will create a new JAXBContext from scratch
     * using the class.
     *
     * @param clazz                class type you are expecting
     * @param otherPossibleClasses Other classe you want to create the JAXBContext with
     * @return null if there is no XML content
     * @throws JAXBException jaxb exception
     */
    @SuppressWarnings(value = "unchecked")
    public <T> T getJAXBObject(Class<T> clazz, Class... otherPossibleClasses) throws JAXBException {
        JAXBContext ctx = null;
        Class[] classes = { clazz };
        if (otherPossibleClasses != null && otherPossibleClasses.length > 0) {
            classes = new Class[1 + otherPossibleClasses.length];
            classes[0] = clazz;
            for (int i = 0; i < otherPossibleClasses.length; i++)
                classes[i + 1] = otherPossibleClasses[i];
        }
        if (finder != null) {
            ctx = finder.findCacheContext(MediaType.APPLICATION_XML_TYPE, null, classes);
        } else {
            ctx = JAXBContext.newInstance(classes);
        }
        if (getElement() == null)
            return null;
        Object obj = ctx.createUnmarshaller().unmarshal(getElement());
        if (obj instanceof JAXBElement) {
            jaxbObject = ((JAXBElement) obj).getValue();
            return (T) jaxbObject;
        } else {
            jaxbObject = obj;
            return (T) obj;
        }
    }

    /**
     * Returns previous extracted jaxbobject from a call to getJAXBObject(Class{@literal <}T{@literal >} clazz)
     * or value passed in through a previous setJAXBObject().
     *
     * @return jaxb object
     */
    @XmlTransient
    public Object getJAXBObject() {
        return jaxbObject;
    }

    public void setJAXBObject(Object obj) {
        if (value == null)
            value = new ArrayList<Object>();
        if (jaxbObject != null && value != null)
            value.clear();
        if (!obj.getClass().isAnnotationPresent(XmlRootElement.class) && obj.getClass().isAnnotationPresent(XmlType.class)) {
            value.add(JAXBXmlTypeProvider.wrapInJAXBElement(obj, obj.getClass()));
        } else {
            value.add(obj);
        }
        jaxbObject = obj;
    }
}
