/*
 * Copyright (C) 2013 4th Line GmbH, Switzerland
 *
 * The contents of this file are subject to the terms of either the GNU
 * Lesser General Public License Version 2 or later ("LGPL") or the
 * Common Development and Distribution License Version 1 or later
 * ("CDDL") (collectively, the "License"). You may not use this file
 * except in compliance with the License. See LICENSE.txt for more
 * information.
 *
 * This program 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.
 */

package org.fourthline.cling.support.model;

import org.w3c.dom.Element;

import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

/**
 * @author Christian Bauer
 * @author Mario Franco
 */
public abstract class DIDLObject {

    static public abstract class Property<V> {

        public interface NAMESPACE {
        }

        private V value;
        final private String descriptorName;
        final private List<Property<DIDLAttribute>> attributes = new ArrayList<Property<DIDLAttribute>>();

        protected Property() {
            this(null, null);
        }

        protected Property(String descriptorName) {
            this(null, descriptorName);
        }

        protected Property(V value, String descriptorName) {
            this.value = value;
            // TODO Not sure this is a good fix for https://github.com/4thline/cling/issues/62
            this.descriptorName = descriptorName == null
                ? getClass().getSimpleName().toLowerCase(Locale.ROOT).replace("didlobject$property$upnp$", "")
                : descriptorName;
        }

        protected Property(V value, String descriptorName, List<Property<DIDLAttribute>> attributes) {
            this.value = value;
            // TODO Not sure this is a good fix for https://github.com/4thline/cling/issues/62
            this.descriptorName = descriptorName == null
                ? getClass().getSimpleName().toLowerCase(Locale.ROOT).replace("didlobject$property$upnp$", "")
                : descriptorName;
            this.attributes.addAll(attributes);
        }

        public V getValue() {
            return value;
        }

        public void setValue(V value) {
            this.value = value;
        }

        public String getDescriptorName() {
            return descriptorName;
        }

        public void setOnElement(Element element) {
            element.setTextContent(toString());
            for (Property<DIDLAttribute> attr : attributes) {
                element.setAttributeNS(
                        attr.getValue().getNamespaceURI(),
                        attr.getValue().getPrefix() + ':' + attr.getDescriptorName(),
                        attr.getValue().getValue());
            }
        }

        public void addAttribute(Property<DIDLAttribute> attr) {
            this.attributes.add(attr);
        }

        public void removeAttribute(Property<DIDLAttribute> attr) {
            this.attributes.remove(attr);
        }

        public void removeAttribute(String descriptorName) {
            for (Property<DIDLAttribute> attr : attributes) {
                if (attr.getDescriptorName().equals(descriptorName)) {
                    this.removeAttribute(attr);
                    break;
                }
            }
        }

        public Property<DIDLAttribute> getAttribute(String descriptorName) {
            for (Property<DIDLAttribute> attr : attributes) {
                if (attr.getDescriptorName().equals(descriptorName)) {
                    return attr;
                }
            }
            return null;
        }

        @Override
        public String toString() {
            return getValue() != null ? getValue().toString() : "";
        }

        static public class PropertyPersonWithRole extends Property<PersonWithRole> {

            public PropertyPersonWithRole() {
            }

            public PropertyPersonWithRole(String descriptorName) {
                super(descriptorName);
            }

            public PropertyPersonWithRole(PersonWithRole value, String descriptorName) {
                super(value, descriptorName);
            }

            @Override
            public void setOnElement(Element element) {
                if (getValue() != null)
                    getValue().setOnElement(element);
            }
        }

        static public class DC {

            public interface NAMESPACE extends Property.NAMESPACE {
                public static final String URI = "http://purl.org/dc/elements/1.1/";
            }

            static public class DESCRIPTION extends Property<String> implements NAMESPACE {
                public DESCRIPTION() {
                }

                public DESCRIPTION(String value) {
                    super(value, null);
                }
            }

            static public class PUBLISHER extends Property<Person> implements NAMESPACE {
                public PUBLISHER() {
                }

                public PUBLISHER(Person value) {
                    super(value, null);
                }
            }

            static public class CONTRIBUTOR extends Property<Person> implements NAMESPACE {
                public CONTRIBUTOR() {
                }

                public CONTRIBUTOR(Person value) {
                    super(value, null);
                }
            }

            static public class DATE extends Property<String> implements NAMESPACE {
                public DATE() {
                }

                public DATE(String value) {
                    super(value, null);
                }
            }

            static public class LANGUAGE extends Property<String> implements NAMESPACE {
                public LANGUAGE() {
                }

                public LANGUAGE(String value) {
                    super(value, null);
                }
            }

            static public class RELATION extends Property<URI> implements NAMESPACE {
                public RELATION() {
                }

                public RELATION(URI value) {
                    super(value, null);
                }
            }

            static public class RIGHTS extends Property<String> implements NAMESPACE {
                public RIGHTS() {
                }

                public RIGHTS(String value) {
                    super(value, null);
                }
            }
        }
        
        static public abstract class SEC {

            public interface NAMESPACE extends Property.NAMESPACE {
                public static final String URI = "http://www.sec.co.kr/";
            }
            
            static public class CAPTIONINFOEX extends Property<URI> implements NAMESPACE {
                public CAPTIONINFOEX() {
                    this(null);
                }
                
                public CAPTIONINFOEX(URI value) {
                    super(value, "CaptionInfoEx");
                }

                public CAPTIONINFOEX(URI value, List<Property<DIDLAttribute>> attributes) {
                    super(value, "CaptionInfoEx", attributes);
                }
            }
            
            static public class CAPTIONINFO extends Property<URI> implements NAMESPACE {
                public CAPTIONINFO() {
                    this(null);
                }
                
                public CAPTIONINFO(URI value) {
                    super(value, "CaptionInfo");
                }

                public CAPTIONINFO(URI value, List<Property<DIDLAttribute>> attributes) {
                    super(value, "CaptionInfo", attributes);
                }
            }
            
            static public class TYPE extends Property<DIDLAttribute> implements NAMESPACE {
                public TYPE() {
                    this(null);
                }

                public TYPE(DIDLAttribute value) {
                    super(value, "type");
                }
            }
            
            
        }

        static public abstract class UPNP {

            public interface NAMESPACE extends Property.NAMESPACE {
                public static final String URI = "urn:schemas-upnp-org:metadata-1-0/upnp/";
            }

            static public class ARTIST extends PropertyPersonWithRole implements NAMESPACE {
                public ARTIST() {
                }

                public ARTIST(PersonWithRole value) {
                    super(value, null);
                }
            }

            static public class ACTOR extends PropertyPersonWithRole implements NAMESPACE {
                public ACTOR() {
                }

                public ACTOR(PersonWithRole value) {
                    super(value, null);
                }
            }

            static public class AUTHOR extends PropertyPersonWithRole implements NAMESPACE {
                public AUTHOR() {
                }

                public AUTHOR(PersonWithRole value) {
                    super(value, null);
                }
            }

            static public class PRODUCER extends Property<Person> implements NAMESPACE {
                public PRODUCER() {
                }

                public PRODUCER(Person value) {
                    super(value, null);
                }
            }

            static public class DIRECTOR extends Property<Person> implements NAMESPACE {
                public DIRECTOR() {
                }

                public DIRECTOR(Person value) {
                    super(value, null);
                }
            }

            static public class GENRE extends Property<String> implements NAMESPACE {
                public GENRE() {
                }

                public GENRE(String value) {
                    super(value, null);
                }
            }

            static public class ALBUM extends Property<String> implements NAMESPACE {
                public ALBUM() {
                }

                public ALBUM(String value) {
                    super(value, null);
                }
            }

            static public class PLAYLIST extends Property<String> implements NAMESPACE {
                public PLAYLIST() {
                }

                public PLAYLIST(String value) {
                    super(value, null);
                }
            }

            static public class REGION extends Property<String> implements NAMESPACE {
                public REGION() {
                }

                public REGION(String value) {
                    super(value, null);
                }
            }

            static public class RATING extends Property<String> implements NAMESPACE {
                public RATING() {
                }

                public RATING(String value) {
                    super(value, null);
                }
            }

            static public class TOC extends Property<String> implements NAMESPACE {
                public TOC() {
                }

                public TOC(String value) {
                    super(value, null);
                }
            }

            static public class ALBUM_ART_URI extends Property<URI> implements NAMESPACE {
                public ALBUM_ART_URI() {
                    this(null);
                }

                public ALBUM_ART_URI(URI value) {
                    super(value, "albumArtURI");
                }

                public ALBUM_ART_URI(URI value, List<Property<DIDLAttribute>> attributes) {
                    super(value, "albumArtURI", attributes);
                }
            }

            static public class ARTIST_DISCO_URI extends Property<URI> implements NAMESPACE {
                public ARTIST_DISCO_URI() {
                    this(null);
                }

                public ARTIST_DISCO_URI(URI value) {
                    super(value, "artistDiscographyURI");
                }
            }

            static public class LYRICS_URI extends Property<URI> implements NAMESPACE {
                public LYRICS_URI() {
                    this(null);
                }

                public LYRICS_URI(URI value) {
                    super(value, "lyricsURI");
                }
            }

            static public class STORAGE_TOTAL extends Property<Long> implements NAMESPACE {
                public STORAGE_TOTAL() {
                    this(null);
                }

                public STORAGE_TOTAL(Long value) {
                    super(value, "storageTotal");
                }
            }

            static public class STORAGE_USED extends Property<Long> implements NAMESPACE {
                public STORAGE_USED() {
                    this(null);
                }

                public STORAGE_USED(Long value) {
                    super(value, "storageUsed");
                }
            }

            static public class STORAGE_FREE extends Property<Long> implements NAMESPACE {
                public STORAGE_FREE() {
                    this(null);
                }

                public STORAGE_FREE(Long value) {
                    super(value, "storageFree");
                }
            }

            static public class STORAGE_MAX_PARTITION extends Property<Long> implements NAMESPACE {
                public STORAGE_MAX_PARTITION() {
                    this(null);
                }

                public STORAGE_MAX_PARTITION(Long value) {
                    super(value, "storageMaxPartition");
                }
            }

            static public class STORAGE_MEDIUM extends Property<StorageMedium> implements NAMESPACE {
                public STORAGE_MEDIUM() {
                    this(null);
                }

                public STORAGE_MEDIUM(StorageMedium value) {
                    super(value, "storageMedium");
                }
            }

            static public class LONG_DESCRIPTION extends Property<String> implements NAMESPACE {
                public LONG_DESCRIPTION() {
                    this(null);
                }

                public LONG_DESCRIPTION(String value) {
                    super(value, "longDescription");
                }
            }

            static public class ICON extends Property<URI> implements NAMESPACE {
                public ICON() {
                }

                public ICON(URI value) {
                    super(value, null);
                }
            }

            static public class RADIO_CALL_SIGN extends Property<String> implements NAMESPACE {
                public RADIO_CALL_SIGN() {
                    this(null);
                }

                public RADIO_CALL_SIGN(String value) {
                    super(value, "radioCallSign");
                }
            }

            static public class RADIO_STATION_ID extends Property<String> implements NAMESPACE {
                public RADIO_STATION_ID() {
                    this(null);
                }

                public RADIO_STATION_ID(String value) {
                    super(value, "radioStationID");
                }
            }

            static public class RADIO_BAND extends Property<String> implements NAMESPACE {
                public RADIO_BAND() {
                    this(null);
                }

                public RADIO_BAND(String value) {
                    super(value, "radioBand");
                }
            }

            static public class CHANNEL_NR extends Property<Integer> implements NAMESPACE {
                public CHANNEL_NR() {
                    this(null);
                }

                public CHANNEL_NR(Integer value) {
                    super(value, "channelNr");
                }
            }

            static public class CHANNEL_NAME extends Property<String> implements NAMESPACE {
                public CHANNEL_NAME() {
                    this(null);
                }

                public CHANNEL_NAME(String value) {
                    super(value, "channelName");
                }
            }

            static public class SCHEDULED_START_TIME extends Property<String> implements NAMESPACE {
                public SCHEDULED_START_TIME() {
                    this(null);
                }

                public SCHEDULED_START_TIME(String value) {
                    super(value, "scheduledStartTime");
                }
            }

            static public class SCHEDULED_END_TIME extends Property<String> implements NAMESPACE {
                public SCHEDULED_END_TIME() {
                    this(null);
                }

                public SCHEDULED_END_TIME(String value) {
                    super(value, "scheduledEndTime");
                }
            }

            static public class DVD_REGION_CODE extends Property<Integer> implements NAMESPACE {
                public DVD_REGION_CODE() {
                    this(null);
                }

                public DVD_REGION_CODE(Integer value) {
                    super(value, "DVDRegionCode");
                }
            }

            static public class ORIGINAL_TRACK_NUMBER extends Property<Integer> implements NAMESPACE {
                public ORIGINAL_TRACK_NUMBER() {
                    this(null);
                }

                public ORIGINAL_TRACK_NUMBER(Integer value) {
                    super(value, "originalTrackNumber");
                }
            }


            static public class USER_ANNOTATION extends Property<String> implements NAMESPACE {
                public USER_ANNOTATION() {
                    this(null);
                }

                public USER_ANNOTATION(String value) {
                    super(value, "userAnnotation");
                }
            }
        }

        static public abstract class DLNA {

            public interface NAMESPACE extends Property.NAMESPACE {
                public static final String URI = "urn:schemas-dlna-org:metadata-1-0/";
            }

            static public class PROFILE_ID extends Property<DIDLAttribute> implements NAMESPACE {
                public PROFILE_ID() {
                    this(null);
                }

                public PROFILE_ID(DIDLAttribute value) {
                    super(value, "profileID");
                }
            }
        }
    }

    public static class Class {

        protected String value;
        protected String friendlyName;
        protected boolean includeDerived;

        public Class() {
        }

        public Class(String value) {
            this.value = value;
        }

        public Class(String value, String friendlyName) {
            this.value = value;
            this.friendlyName = friendlyName;
        }

        public Class(String value, String friendlyName, boolean includeDerived) {
            this.value = value;
            this.friendlyName = friendlyName;
            this.includeDerived = includeDerived;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public String getFriendlyName() {
            return friendlyName;
        }

        public void setFriendlyName(String friendlyName) {
            this.friendlyName = friendlyName;
        }

        public boolean isIncludeDerived() {
            return includeDerived;
        }

        public void setIncludeDerived(boolean includeDerived) {
            this.includeDerived = includeDerived;
        }

        public boolean equals(DIDLObject instance) {
            return getValue().equals(instance.getClazz().getValue());

        }
    }

    protected String id;
    protected String parentID;

    protected String title; // DC
    protected String creator; // DC

    protected boolean restricted = true; // Let's just assume read-only is default
    protected WriteStatus writeStatus; // UPNP
    protected Class clazz; // UPNP

    protected List<Res> resources = new ArrayList();
    protected List<Property> properties = new ArrayList();

    protected List<DescMeta> descMetadata = new ArrayList();

    protected DIDLObject() {
    }

    protected DIDLObject(DIDLObject other) {
        this(other.getId(),
             other.getParentID(),
             other.getTitle(),
             other.getCreator(),
             other.isRestricted(),
             other.getWriteStatus(),
             other.getClazz(),
             other.getResources(),
             other.getProperties(),
             other.getDescMetadata()
        );
    }

    protected DIDLObject(String id, String parentID, String title, String creator, boolean restricted, WriteStatus writeStatus, Class clazz, List<Res> resources, List<Property> properties, List<DescMeta> descMetadata) {
        this.id = id;
        this.parentID = parentID;
        this.title = title;
        this.creator = creator;
        this.restricted = restricted;
        this.writeStatus = writeStatus;
        this.clazz = clazz;
        this.resources = resources;
        this.properties = properties;
        this.descMetadata = descMetadata;
    }

    public String getId() {
        return id;
    }

    public DIDLObject setId(String id) {
        this.id = id;
        return this;
    }

    public String getParentID() {
        return parentID;
    }

    public DIDLObject setParentID(String parentID) {
        this.parentID = parentID;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public DIDLObject setTitle(String title) {
        this.title = title;
        return this;
    }

    public String getCreator() {
        return creator;
    }

    public DIDLObject setCreator(String creator) {
        this.creator = creator;
        return this;
    }

    public boolean isRestricted() {
        return restricted;
    }

    public DIDLObject setRestricted(boolean restricted) {
        this.restricted = restricted;
        return this;
    }

    public WriteStatus getWriteStatus() {
        return writeStatus;
    }

    public DIDLObject setWriteStatus(WriteStatus writeStatus) {
        this.writeStatus = writeStatus;
        return this;
    }

    public Res getFirstResource() {
        return getResources().size() > 0 ? getResources().get(0) : null;
    }

    public List<Res> getResources() {
        return resources;
    }

    public DIDLObject setResources(List<Res> resources) {
        this.resources = resources;
        return this;
    }

    public DIDLObject addResource(Res resource) {
        getResources().add(resource);
        return this;
    }

    public Class getClazz() {
        return clazz;
    }

    public DIDLObject setClazz(Class clazz) {
        this.clazz = clazz;
        return this;
    }

    public List<Property> getProperties() {
        return properties;
    }

    public DIDLObject setProperties(List<Property> properties) {
        this.properties = properties;
        return this;
    }

    public DIDLObject addProperty(Property property) {
        if (property == null) return this;
        getProperties().add(property);
        return this;
    }

    public DIDLObject replaceFirstProperty(Property property) {
        if (property == null) return this;
        Iterator<Property> it = getProperties().iterator();
        while (it.hasNext()) {
            Property p = it.next();
            if (p.getClass().isAssignableFrom(property.getClass()))
                it.remove();
        }
        addProperty(property);
        return this;
    }

    public DIDLObject replaceProperties(java.lang.Class<? extends Property> propertyClass, Property[] properties) {
        if (properties.length == 0) return this;
        removeProperties(propertyClass);
        return addProperties(properties);
    }

    public DIDLObject addProperties(Property[] properties) {
        if (properties == null) return this;
        for (Property property : properties) {
            addProperty(property);
        }
        return this;
    }

    public DIDLObject removeProperties(java.lang.Class<? extends Property> propertyClass) {
        Iterator<Property> it = getProperties().iterator();
        while (it.hasNext()) {
            Property p = it.next();
            if (p.getClass().isAssignableFrom(propertyClass))
                it.remove();
        }
        return this;
    }

    public boolean hasProperty(java.lang.Class<? extends Property> propertyClass) {
        for (Property property : getProperties()) {
            if (property.getClass().isAssignableFrom(propertyClass)) return true;
        }
        return false;
    }

    public <V> Property<V> getFirstProperty(java.lang.Class<? extends Property<V>> propertyClass) {
        for (Property property : getProperties()) {
            if (property.getClass().isAssignableFrom(propertyClass)) return property;
        }
        return null;
    }

    public <V> Property<V> getLastProperty(java.lang.Class<? extends Property<V>> propertyClass) {
        Property found = null;
        for (Property property : getProperties()) {
            if (property.getClass().isAssignableFrom(propertyClass)) found = property;
        }
        return found;
    }

    public <V> Property<V>[] getProperties(java.lang.Class<? extends Property<V>> propertyClass) {
        List<Property<V>> list = new ArrayList();
        for (Property property : getProperties()) {
            if (property.getClass().isAssignableFrom(propertyClass))
                list.add(property);
        }
        return list.toArray(new Property[list.size()]);
    }

    public <V> Property<V>[] getPropertiesByNamespace(java.lang.Class<? extends Property.NAMESPACE> namespace) {
        List<Property<V>> list = new ArrayList();
        for (Property property : getProperties()) {
            if (namespace.isAssignableFrom(property.getClass()))
                list.add(property);
        }
        return list.toArray(new Property[list.size()]);
    }

    public <V> V getFirstPropertyValue(java.lang.Class<? extends Property<V>> propertyClass) {
        Property<V> prop = getFirstProperty(propertyClass);
        return prop == null ? null : prop.getValue();
    }

    public <V> List<V> getPropertyValues(java.lang.Class<? extends Property<V>> propertyClass) {
        List<V> list = new ArrayList();
        for (Property property : getProperties(propertyClass)) {
            list.add((V) property.getValue());
        }
        return list;
    }

    public List<DescMeta> getDescMetadata() {
        return descMetadata;
    }

    public void setDescMetadata(List<DescMeta> descMetadata) {
        this.descMetadata = descMetadata;
    }

    public DIDLObject addDescMetadata(DescMeta descMetadata) {
        getDescMetadata().add(descMetadata);
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        DIDLObject that = (DIDLObject) o;

        if (!id.equals(that.id)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }
}
