/*
 * Copyright (c) 2011-2019, PCJ Library, Marek Nowicki
 * All rights reserved.
 *
 * Licensed under New BSD License (3-clause license).
 *
 * See the file "LICENSE" for the full license governing this code.
 */
package org.pcj;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
import org.pcj.internal.InternalExecutionBuilder;

/**
 * @author Marek Nowicki (faramir@mat.umk.pl)
 */
final public class ExecutionBuilder extends InternalExecutionBuilder {

    private static final String[] EMPTY_ARRAY = new String[0];
    private final Class<? extends StartPoint> startPoint;
    private final List<Object> nodeList;
    private final Properties properties;

    /**
     * Should be created using {@link PCJ#executionBuilder(Class)}.
     *
     * @param startPoint startPoint class
     */
    ExecutionBuilder(Class<? extends StartPoint> startPoint) {
        this.startPoint = startPoint;

        this.nodeList = new ArrayList<>();
        this.properties = new Properties();
    }

    /**
     * Starts PCJ calculations on local node using specified StartPoint class.
     * <p>
     * List of all hostnames used in calculations should be defined earlier.
     * If none defined single thread on current JVM is used.
     */
    public void start() {
        String[] nodes;
        if (nodeList.isEmpty()) {
            nodes = new String[]{""};
        } else {
            nodes = nodeList.toArray(EMPTY_ARRAY);
        }
        super.start(startPoint, nodes, properties);
    }

    /**
     * Deploys and starts PCJ calculations on nodes using specified StartPoint class.
     * <p>
     * List of all hostnames used in calculations should be defined earlier.
     * If none defined, single thread on current JVM is used.
     */
    public void deploy() {
        String[] nodes;
        if (nodeList.isEmpty()) {
            nodes = new String[]{""};
        } else {
            nodes = nodeList.toArray(EMPTY_ARRAY);
        }
        super.deploy(startPoint, nodes, properties);
    }


    /**
     * Adds node to execution builder configuration.
     * <p>
     * Hostname can be specified many times, so more than one instance of PCJ (PCJ thread) will be run on node.
     * Empty hostnames means current JVM.
     * <p>
     * Hostnames can take port (after colon ':'), eg. <i>localhost:8000</i>.
     * Default port is 8091 and can be modified using <tt>pcj.port</tt> system property value (-Dpcj.port=8091).
     * <p>
     * Hostnames can be specified many times, so more than one instance of PCJ will be run on node (called threads).
     *
     * @param node hostname of node
     * @return a reference to this object
     */
    public ExecutionBuilder addNode(String node) {
        nodeList.add(node);
        return this;
    }

    /**
     * Adds multiple nodes to execution builder configuration.
     * <p>
     * For description of a single hostname see {@link #addNode(String)}.
     *
     * @param nodes array of hostnames
     * @return a reference to this object
     */
    public ExecutionBuilder addNodes(String[] nodes) {
        Collections.addAll(nodeList, nodes);
        return this;
    }

    /**
     * Adds multiple nodes to execution builder configuration using data from file.
     * <p>
     * For description of a single hostname see {@link #addNode(String)}.
     *
     * @param nodeFile file with list of hostnames
     * @return a reference to this object
     * @throws IOException if an I/O error occurs opening the file
     */
    public ExecutionBuilder addNodes(File nodeFile) throws IOException {
        try (Stream<String> lines = Files.lines(Paths.get(nodeFile.getPath()))) {
            lines.forEach(nodeList::add);
        }
        return this;
    }

    /**
     * Adds property to execution builder configuration, possibly replacing it.
     *
     * @param key   the key to be placed into this property list.
     * @param value the value corresponding to key.
     * @return a reference to this object
     */
    public ExecutionBuilder addProperty(String key, String value) {
        properties.setProperty(key, value);
        return this;
    }

    /**
     * Adds all properties parameters from {@link Properties} object, possibly replacing previous values.
     *
     * @param props properties to read from
     * @return a reference to this object
     */
    public ExecutionBuilder addProperties(Properties props) {
        for (String key : props.stringPropertyNames()) {
            String value = props.getProperty(key);
            properties.setProperty(key, value);
        }
        return this;
    }
}

@@ -581,20 +581,20 @@
  */%0A
-final 
 public
+ final
  cla
@@ -646,16 +646,37 @@
 Builder 
+implements Cloneable 
 %7B%0A%0A    p
@@ -807,25 +807,19 @@
 ate 
-final List%3CObject
+List%3CString
 %3E no
@@ -838,22 +838,16 @@
 private 
-final 
 Properti
@@ -1136,24 +1136,24 @@
 ayList%3C%3E();%0A
-
         this
@@ -1183,32 +1183,634 @@
 rties();%0A    %7D%0A%0A
+    @Override%0A    public ExecutionBuilder clone() %7B%0A        try %7B%0A            ExecutionBuilder cloned = (ExecutionBuilder) super.clone();%0A            cloned.nodeList = new ArrayList%3C%3E(this.nodeList);%0A            cloned.properties = (Properties) this.properties.clone();%0A            return cloned;%0A        %7D catch (CloneNotSupportedException e) %7B%0A            throw new InternalError(e);%0A        %7D%0A    %7D%0A%0A    @Override%0A    public String toString() %7B%0A        return %22ExecutionBuilder%7BstartPoint=%22 + startPoint%0A                       + %22, nodeList=%22 + nodeList + %22, properties=%22 + properties + %22%7D%22;%0A    %7D%0A%0A
     /**%0A     * S

Make possibility to clone ExecutionBuilder.