/*
 * Copyright 2015 Time Warner Cable, Inc.
 *
 * 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 com.twcable.jackalope.impl.sling;

import com.day.cq.dam.api.Asset;
import com.day.cq.wcm.api.Page;
import com.twcable.jackalope.JcrConstants;
import com.twcable.jackalope.impl.common.Paths;
import com.twcable.jackalope.impl.cq.AssetImpl;
import com.twcable.jackalope.impl.cq.PageImpl;
import com.twcable.jackalope.impl.jcr.JcrUtils;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistableValueMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.jcr.resource.JcrModifiablePropertyMap;
import org.apache.sling.jcr.resource.JcrPropertyMap;
import org.apache.sling.jcr.resource.internal.JcrModifiableValueMap;

import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT;
import static org.apache.jackrabbit.JcrConstants.JCR_DATA;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.JcrConstants.NT_FILE;

/**
 * Implement sling Resource for a jcr Node
 */
public class NodeResourceImpl extends ItemResourceImpl {
    private final Node node;


    /**
     * Construct a new NodeResourceImpl.
     *
     * @param resourceResolver ResourceResolver that constructed this NodeResourceImpl
     * @param node             The constructed ResourceNodeImpl
     */
    public NodeResourceImpl(ResourceResolver resourceResolver, Node node) {
        super(resourceResolver, node);
        this.node = node;
    }


    @Override
    public Iterator<Resource> listChildren() {
        return getChildren().iterator();
    }


    @Override
    public Iterable<Resource> getChildren() {
        try {
            List<Resource> children = new ArrayList<>();
            for (Node child : JcrUtils.getChildNodes(node))
                children.add(resourceResolver.getResource(this, child.getName()));
            return children;
        }
        catch (RepositoryException re) {
            return new ArrayList<>();
        }
    }


    @Override
    public Resource getChild(String relPath) {
        try {
            return resourceResolver.getResource(Paths.resolve(node.getPath(), relPath));
        }
        catch (RepositoryException re) {
            throw new SlingRepositoryException(re);
        }
    }


    @Override
    public String getResourceType() {
        try {
            return getResourceTypeForNode(node);
        }
        catch (RepositoryException re) {
            return null; /* ignore */
        }
    }


    @Override
    public String getResourceSuperType() {
        return null; //TODO: implement resource supertypes
    }


    @Override
    public boolean hasChildren() {
        return listChildren().hasNext();
    }


    @Override
    public ValueMap getValueMap() {
        return this.adaptTo(ValueMap.class);
    }


    @Override
    @SuppressWarnings({"unchecked", "deprecation"})
    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
        if (type.equals(Node.class)) return (AdapterType)node;
        if (type.equals(ValueMap.class) || type.equals(Map.class)) return (AdapterType)new JcrPropertyMap(node);
        if (type.equals(PersistableValueMap.class)) return (AdapterType)new JcrModifiablePropertyMap(node);
        if (type.equals(ModifiableValueMap.class)) return (AdapterType)new JcrModifiableValueMap(node, null);

        if (type.equals(Page.class)) {
            try {
                ValueMap properties = this.adaptTo(ValueMap.class);
                if (properties == null) return null;
                String primaryType = properties.get(JCR_PRIMARYTYPE, String.class);
                if (primaryType == null || !primaryType.equals(JcrConstants.CQ_PAGE)) return null;
                if (!node.hasNode(JCR_CONTENT)) return null;
                return (AdapterType)new PageImpl(new NodeResourceImpl(resourceResolver, node));
            }
            catch (RepositoryException e) {
                return null;
            }
        }

        if (type.equals(Asset.class)) {
            try {
                ValueMap properties = ResourceUtil.getValueMap(this);
                return !"dam:Asset".equals(properties.get(JCR_PRIMARYTYPE, String.class)) ? null :
                    !node.hasNode(JCR_CONTENT) ? null :
                        (AdapterType)new AssetImpl(this);
            }
            catch (RepositoryException e) {
                return null;
            }
        }

        if (type.equals(InputStream.class)) {
            try {
                Node content = node.isNodeType(NT_FILE) ? node.getNode(JCR_CONTENT) : node;
                Property data = content.hasProperty(JCR_DATA) ? content.getProperty(JCR_DATA) : null;
                return data != null ? (AdapterType)data.getBinary().getStream() : null;

            }
            catch (RepositoryException e) {
                return null;
            }
        }

        return super.adaptTo(type);
    }

}