/**
 * 
 */
package com.thinkbiganalytics.metadata.modeshape.feed;

/*-
 * #%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 java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;

import org.joda.time.DateTime;

import com.thinkbiganalytics.metadata.api.feed.Feed;
import com.thinkbiganalytics.metadata.api.feed.Feed.State;
import com.thinkbiganalytics.metadata.api.security.HadoopSecurityGroup;
import com.thinkbiganalytics.metadata.api.feed.InitializationStatus;
import com.thinkbiganalytics.metadata.modeshape.MetadataRepositoryException;
import com.thinkbiganalytics.metadata.modeshape.common.JcrPropertiesEntity;
import com.thinkbiganalytics.metadata.modeshape.security.JcrHadoopSecurityGroup;
import com.thinkbiganalytics.metadata.modeshape.support.JcrPropertyUtil;
import com.thinkbiganalytics.metadata.modeshape.support.JcrUtil;
import com.thinkbiganalytics.metadata.sla.api.ServiceLevelAgreement;

/**
 *
 */
public class FeedData extends JcrPropertiesEntity {

    public static final String NODE_TYPE = "tba:feedData";

    public static final String HIGH_WATER_MARKS = "tba:highWaterMarks";
    public static final String WATER_MARKS_TYPE = "tba:waterMarks";

    public static final String INITIALIZATION_TYPE = "tba:initialization";
    public static final String INIT_STATUS_TYPE = "tba:initStatus";
    public static final String INITIALIZATION = "tba:initialization";
    public static final String INIT_HISTORY = "tba:history";
    public static final int MAX_INIT_HISTORY = 10;
    public static final String INIT_STATE = "tba:state";
    public static final String CURRENT_INIT_STATUS = "tba:currentStatus";

    public static final String STATE = INIT_STATE;

    public static final String SCHEDULE_PERIOD = "tba:schedulingPeriod"; // Cron expression, or Timer Expression
    public static final String SCHEDULE_STRATEGY = "tba:schedulingStrategy"; //CRON_DRIVEN, TIMER_DRIVEN
    public static final String HADOOP_SECURITY_GROUPS = "tba:securityGroups";

    public static final String USR_PREFIX = "usr:";

    
    public FeedData(Node node) {
        super(node);
    }

    public State getState() {
        return getProperty(STATE, Feed.State.ENABLED);
    }

    public void setState(State state) {
        setProperty(STATE, state);
    }

    public boolean isInitialized() {
        // TODO this smells like a bug
        return false;
    }

    public InitializationStatus getCurrentInitStatus() {
        if (JcrUtil.hasNode(getNode(), INITIALIZATION)) {
            Node initNode = JcrUtil.getNode(getNode(), INITIALIZATION);
            Node statusNode = JcrPropertyUtil.getProperty(initNode, CURRENT_INIT_STATUS);
            return createInitializationStatus(statusNode);
        } else {
            return new InitializationStatus(InitializationStatus.State.PENDING);
        }
    }

    public void updateInitStatus(InitializationStatus status) {
        try {
            Node initNode = JcrUtil.getOrCreateNode(getNode(), INITIALIZATION, INITIALIZATION, true);
            Node statusNode = initNode.addNode(INIT_HISTORY, INIT_STATUS_TYPE);
            statusNode.setProperty(INIT_STATE, status.getState().toString());
            initNode.setProperty(CURRENT_INIT_STATUS, statusNode);

            // Trim the history if necessary
            NodeIterator itr = initNode.getNodes(INIT_HISTORY);
            if (itr.getSize() > MAX_INIT_HISTORY) {
                long excess = itr.getSize() - MAX_INIT_HISTORY;
                for (int cnt = 0; cnt < excess; cnt++) {
                    Node node = itr.nextNode();
                    node.remove();
                }
            }
        } catch (RepositoryException e) {
            throw new MetadataRepositoryException("Failed to access initializations statuses", e);
        }
    }

    public List<InitializationStatus> getInitHistory() {
        Node initNode = JcrUtil.getNode(getNode(), INITIALIZATION);

        if (initNode != null) {
            return JcrUtil.getNodeList(initNode, INIT_HISTORY).stream()
                .map(n -> createInitializationStatus(n))
                .sorted(Comparator.comparing(InitializationStatus::getTimestamp).reversed())
                .limit(MAX_INIT_HISTORY)
                .collect(Collectors.toList());
        } else {
            return Collections.emptyList();
        }
    }
    
    public Set<String> getWaterMarkNames() {
        if (JcrUtil.hasNode(getNode(), HIGH_WATER_MARKS)) {
            Node wmNode = JcrUtil.getNode(getNode(), HIGH_WATER_MARKS);
            return JcrPropertyUtil.streamProperties(wmNode)
                .map(JcrPropertyUtil::getName)
                .filter(name -> name.startsWith(USR_PREFIX))
                .map(name -> name.replace(USR_PREFIX, ""))
                .collect(Collectors.toSet());
        } else {
            return Collections.emptySet();
        }
    }

    public Optional<String> getWaterMarkValue(String waterMarkName) {
        if (JcrUtil.hasNode(getNode(), HIGH_WATER_MARKS)) {
            Node wmNode = JcrUtil.getNode(getNode(), HIGH_WATER_MARKS);
            return JcrPropertyUtil.findProperty(wmNode, USR_PREFIX + waterMarkName).map(JcrPropertyUtil::toString);
        } else {
            return Optional.empty();
        }
    }
    
    public void setWaterMarkValue(String waterMarkName, String value) {
        Node wmNode = JcrUtil.getOrCreateNode(getNode(), HIGH_WATER_MARKS, WATER_MARKS_TYPE);
        JcrPropertyUtil.setProperty(wmNode, USR_PREFIX + waterMarkName, value);
    }

    public String getSchedulePeriod() {
        return getProperty(SCHEDULE_PERIOD, String.class);
    }

    public void setSchedulePeriod(String schedulePeriod) {
        setProperty(SCHEDULE_PERIOD, schedulePeriod);
    }

    public String getScheduleStrategy() {
        return getProperty(SCHEDULE_STRATEGY, String.class);
    }

    public void setScheduleStrategy(String scheduleStrategy) {
        setProperty(SCHEDULE_STRATEGY, scheduleStrategy);
    }

    public List<? extends HadoopSecurityGroup> getSecurityGroups() {
        Set<Node> list = JcrPropertyUtil.getReferencedNodeSet(this.node, HADOOP_SECURITY_GROUPS);
        List<HadoopSecurityGroup> hadoopSecurityGroups = new ArrayList<>();
        if (list != null) {
            for (Node n : list) {
                hadoopSecurityGroups.add(JcrUtil.createJcrObject(n, JcrHadoopSecurityGroup.class));
            }
        }
        return hadoopSecurityGroups;
    }

    public void setSecurityGroups(List<? extends HadoopSecurityGroup> hadoopSecurityGroups) {
        JcrPropertyUtil.setProperty(this.node, HADOOP_SECURITY_GROUPS, null);

        for (HadoopSecurityGroup securityGroup : hadoopSecurityGroups) {
            Node securityGroupNode = ((JcrHadoopSecurityGroup) securityGroup).getNode();
            JcrPropertyUtil.addToSetProperty(this.node, HADOOP_SECURITY_GROUPS, securityGroupNode, true);
        }
    }

    private InitializationStatus createInitializationStatus(Node statusNode) {
        InitializationStatus.State state = InitializationStatus.State.valueOf(JcrPropertyUtil.getString(statusNode, INIT_STATE));
        DateTime timestamp = JcrPropertyUtil.getProperty(statusNode, "jcr:created");
        return new InitializationStatus(state, timestamp);
    }

}
