package com.thinkbiganalytics.metadata.modeshape.datasource;

/*-
 * #%L
 * kylo-metadata-modeshape
 * %%
 * Copyright (C) 2017 ThinkBig Analytics
 * %%
 * 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%
 */

import com.thinkbiganalytics.metadata.api.datasource.DatasourceDetails;
import com.thinkbiganalytics.metadata.api.datasource.JdbcDatasourceDetails;
import com.thinkbiganalytics.metadata.api.datasource.UserDatasource;
import com.thinkbiganalytics.metadata.modeshape.MetadataRepositoryException;
import com.thinkbiganalytics.metadata.modeshape.datasource.security.JcrDatasourceAllowedActions;
import com.thinkbiganalytics.metadata.modeshape.security.action.JcrAllowedActions;
import com.thinkbiganalytics.metadata.modeshape.security.mixin.AccessControlledMixin;
import com.thinkbiganalytics.metadata.modeshape.support.JcrUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.annotation.Nonnull;
import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;

/**
 * A {@code UserDatasource} that is stored in JRC.
 */
public class JcrUserDatasource extends JcrDatasource implements UserDatasource, AccessControlledMixin {

    private static final Logger log = LoggerFactory.getLogger(JcrUserDatasource.class);
    private static final long serialVersionUID = -6114070693323660125L;

    /**
     * JCR node type
     */
    public static String NODE_TYPE = "tba:userDatasource";

    /**
     * Sub-node under {@code datasources} node for {@code userDatasource} nodes
     */
    @SuppressWarnings("unused")
    public static String PATH_NAME = "user";

    /**
     * Name of the details attribute
     */
    static final String DETAILS = "tba:details";

    /**
     * Mapping from domain type to JCR type
     */
    private static final Map<Class<? extends DatasourceDetails>, Class<? extends JcrDatasourceDetails>> DETAILS_DOMAIN_TYPES_MAP = new HashMap<>();

    static {
        DETAILS_DOMAIN_TYPES_MAP.put(JdbcDatasourceDetails.class, JcrJdbcDatasourceDetails.class);
    }

    /**
     * Mapping from JCR type name to JCR type class
     */
    private static final Map<String, Class<? extends JcrDatasourceDetails>> DETAILS_NODE_TYPES_MAP = new HashMap<>();

    static {
        DETAILS_NODE_TYPES_MAP.put(JcrJdbcDatasourceDetails.NODE_TYPE, JcrJdbcDatasourceDetails.class);
    }

    /**
     * Name of the type attribute
     */
    private static final String TYPE = "tba:type";

    @Nonnull
    static Class<? extends JcrDatasourceDetails> resolveDetailsClass(@Nonnull final Class<? extends DatasourceDetails> domainClass) {
        return DETAILS_DOMAIN_TYPES_MAP.getOrDefault(domainClass, JcrDatasourceDetails.class);
    }

    /**
     * Details child node.
     */
    private JcrDatasourceDetails details;

    /**
     * Constructs a {@code JcrUserDatasource} with the specified JCR node.
     *
     * @param node the JCR node
     */
    public JcrUserDatasource(@Nonnull final Node node) {
        super(node);
    }

    @Nonnull
    public Optional<? extends JcrDatasourceDetails> getDetails() {
        if (details == null && JcrUtil.hasNode(getNode(), DETAILS)) {
            details = getDetailsNode()
                .map(this::toDetailsObject)
                .orElse(null);
        }
        return Optional.ofNullable(details);
    }

    @Override
    public Class<? extends JcrAllowedActions> getJcrAllowedActionsType() {
        return JcrDatasourceAllowedActions.class;
    }

    @Override
    public void setName(@Nonnull final String name) {
        setProperty(TITLE, name);
    }

    @Nonnull
    @Override
    public String getType() {
        return getProperty(TYPE, String.class);
    }

    @Override
    public void setType(@Nonnull final String type) {
        setProperty(TYPE, type);
    }

    @Nonnull
    private Optional<Node> getDetailsNode() {
        try {
            return Optional.ofNullable(getNode().getNode(DETAILS));
        } catch (final AccessDeniedException e) {
            log.debug("Access denied to details node for user data source: ", getId(), e);
            return Optional.empty();
        } catch (final PathNotFoundException e) {
            return Optional.empty();
        } catch (final RepositoryException e) {
            throw new MetadataRepositoryException("Failure while finding details node for user data source: " + getId(), e);
        }
    }

    @Nonnull
    private JcrDatasourceDetails toDetailsObject(@Nonnull final Node detailsNode) {
        // Determine type class
        final Class<? extends JcrDatasourceDetails> typeClass;

        try {
            final String typeName = detailsNode.getPrimaryNodeType().getName();
            typeClass = DETAILS_NODE_TYPES_MAP.getOrDefault(typeName, JcrDatasourceDetails.class);
        } catch (final RepositoryException e) {
            throw new MetadataRepositoryException("Failed to determine type of node: " + detailsNode, e);
        }

        // Convert node to object
        return JcrUtil.getJcrObject(detailsNode, typeClass);
    }
}
