package com.capgemini.framework.teamcenter.files;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.MalformedDataSetException;
import com.capgemini.framework.teamcenter.files.FileManagement.FileType;
import com.google.common.base.Optional;
import com.teamcenter.services.loose.core._2006_03.FileManagement.DatasetFileInfo;
import com.teamcenter.services.loose.core._2006_03.FileManagement.GetDatasetWriteTicketsInputData;
import com.teamcenter.services.strong.core._2010_04.DataManagement.DatasetInfo;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.io.File;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

/**
 * This class compiles and prepare the necessary data for creating a dataset, then is able to create it on the TC servers.<br>
 * The dataset may contain several files.
 */
@Getter(value = AccessLevel.PROTECTED)
public class DatasetPreparator {

    /**
     * constant for debugging and retrieving the origin of a dataset.
     */
    private static final String CLIENT_ID = "KurtReport_FromWorkflow";

    /**
     * We apparently cannot upload two files with the same name to the same Dataset because this creates a relation name conflict.<br>
     * So this comparator ensures there are no two files with the same filenames.
     */
    private static final Comparator<DataBundle> fileNameComparator = new Comparator<DataBundle>() {
        @Override
        public int compare(final DataBundle obj1, final DataBundle obj2) {
            return obj1.getDatasetInfo().fileName.compareTo(obj2.getDatasetInfo().fileName);
        }
    };

    private final String datasetName;
    private final String datasetDescription;
    private final Set<DataBundle> filesInDataSet = new TreeSet<>(fileNameComparator);
    private final TcSession session;
    private Optional<GetDatasetWriteTicketsInputData> datasetTicket = Optional.absent();

    /**
     * Constructs a {@link DatasetPreparator}, defines its name and description.
     */
    public DatasetPreparator(final TcSession session, final String datasetName, final String datasetDescription) throws MalformedDataSetException {
        this.session = session;
        if (datasetName == null || datasetName.isEmpty()) {
            throw new MalformedDataSetException("Insufficient parameters for dataset creation");
        }
        this.datasetName = datasetName;
        this.datasetDescription = datasetDescription == null ? "" : datasetDescription;
    }

    /**
     * Returns true if a dataset ticket has been created.
     *
     * @return true if a dataset ticket has been created.
     */
    public boolean isCreated() {
        return datasetTicket.isPresent();
    }

    /**
     * Obtain the Dataset ticket (a {@link GetDatasetWriteTicketsInputData} object). <br>
     * This method only works if {@link DatasetPreparator#isCreated()} is <code>true</code>.
     */
    public GetDatasetWriteTicketsInputData getCreatedDataset() {
        return datasetTicket.get();
    }

    /**
     * Add a file to the dataset.
     * <p>
     * Be careful, no two files may have the same name:<br>
     * If a file already exists with the same name, the new file will NOT be added, and the method will return false (just like in {@link Set} definition).
     *
     * @param file the File to be added (please ensure the file does exist)
     * @param type the {@link FileType} of the file
     * @return true if the operation is successful, false if not (maybe a file with same name already exists?)
     * @throws MalformedDataSetException when the provided File is null, does not exist, or name is null, empty, or no type is specified
     */
    public boolean addFile(final File file, final FileType type) throws MalformedDataSetException {
        // Pre conditions
        if (file == null || !file.exists()) {
            throw new MalformedDataSetException("File for dataset does not exist");
        }

        // Prepare file info
        DatasetFileInfo fileInfo = new DatasetFileInfo();
        fileInfo.clientId = CLIENT_ID;
        fileInfo.fileName = file.getAbsolutePath();
        fileInfo.namedReferencedName = type.getNamedReferenceName();
        fileInfo.isText = type.isText();
        fileInfo.allowReplace = false;


        // Prepare dataset request properties
        DatasetInfo properties = new DatasetInfo();
        properties.clientId = CLIENT_ID;
        properties.type = type.getType();
        properties.name = datasetName;
        properties.description = datasetDescription;

        // Store this demand
        return filesInDataSet.add(new DataBundle(fileInfo, properties));
    }

    /**
     * Creates the dataset on the TC servers.
     */
    public GetDatasetWriteTicketsInputData prepare() throws MalformedDataSetException {
        if (filesInDataSet.isEmpty()) {
            throw new MalformedDataSetException("Insufficient parameters for dataset creation");
        }
        if (isCreated()) {
            throw new MalformedDataSetException("This Dataset has already been created, cannot ask to create it twice");
        }

        DatasetInfo[] currProps = new DatasetInfo[filesInDataSet.size()];
        DatasetFileInfo[] currFiles = new DatasetFileInfo[filesInDataSet.size()];
        int num = 0;
        for (DataBundle bundle : filesInDataSet) {
            currProps[num] = bundle.getDatasetProperties();
            currFiles[num] = bundle.getDatasetInfo();
            num++;
        }

        GetDatasetWriteTicketsInputData dataSetInfoAndData = session.createDatasets(currProps, currFiles);
        datasetTicket = Optional.of(dataSetInfoAndData);
        return dataSetInfoAndData;
    }

    /**
     * Tells if this dataset does not have any files into it.
     *
     * @return true if no files are specified
     */
    public boolean isEmpty() {
        return filesInDataSet.isEmpty();
    }

    @AllArgsConstructor
    @Getter
    private static class DataBundle {
        private final DatasetFileInfo datasetInfo;
        private final DatasetInfo datasetProperties;
    }
}
