/*
 * Copyright (c) 2004-2020 The YAWL Foundation. All rights reserved.
 * The YAWL Foundation is a collaboration of individuals and
 * organisations who are committed to improving workflow technology.
 *
 * This file is part of YAWL. YAWL is free software: you can
 * redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation.
 *
 * YAWL is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with YAWL. If not, see <http://www.gnu.org/licenses/>.
 */

package org.yawlfoundation.yawl.balancer;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.yawlfoundation.yawl.balancer.config.Config;
import org.yawlfoundation.yawl.util.JDOMUtil;

import java.util.List;

/**
 * @author Michael Adams
 * @date 11/6/18
 */
public class ComplexityMetric {

    private static final Namespace YAWL_NAMESPACE = Namespace.getNamespace("yawl",
            "http://www.yawlfoundation.org/yawlschema");
    private static final Namespace XSD_NAMESPACE = Namespace.getNamespace("xs",
            "http://www.w3.org/2001/XMLSchema");



    public double calc(String specXML) {
        Document doc = JDOMUtil.stringToDocument(specXML);
        if (doc == null) return -1;
        List<Element> elementList = JDOMUtil.selectElementList(
                doc, "//yawl:task", YAWL_NAMESPACE);
        return getWeightedCarduso(elementList) + getWeightedTaskCardinality(elementList) +
                getWeightedUDTCount(doc) + getWeightedIOCount(elementList) +
                getWeightedResourceCount(doc);
    }


    public double getWeightedCarduso(List<Element> taskElements) {
        double weight = Config.getCardusoComplexityWeight();
        return taskElements == null || weight <= 0 ? 0 :
                getCardusoMetric(taskElements) * weight;
    }


    public double getWeightedTaskCardinality(List<Element> taskElements) {
        double weight = Config.getTaskCardinalityComplexityWeight();
        return taskElements == null || weight <= 0 ? 0 :
                taskElements.size() * weight;
    }


    public double getWeightedUDTCount(Document doc) {
        double weight = Config.getUDTComplexityWeight();
        if (doc == null || weight <= 0) return 0;
        Element root = doc.getRootElement();
        Element spec = root.getChild("specification", YAWL_NAMESPACE);
        Element dataSchema = spec.getChild("schema", XSD_NAMESPACE);
        return dataSchema.getChildren().size() * weight;
    }


    public double getWeightedIOCount(List<Element> taskElements) {
        double weight = Config.getDataMappingsComplexityWeight();
        if (taskElements == null || weight <= 0) return 0;
        int mappingCount = 0;
        for (Element te : taskElements) {
            mappingCount += te.getChildren("startingMappings", YAWL_NAMESPACE).size();
            mappingCount += te.getChildren("completedMappings", YAWL_NAMESPACE).size();
        }
        return mappingCount * weight;
    }


    public double getWeightedResourceCount(Document doc) {
        double weight = Config.getResourcingComplexityWeight();
        if (doc == null || weight <= 0) return 0;
        List<Element> elementList = JDOMUtil.selectElementList(
                doc, "//yawl:role", YAWL_NAMESPACE);
        return elementList.size() + weight;
    }


    private int getCardusoMetric(List<Element> taskElements) {
        int metric = 0;
        for (Element te : taskElements) {
            int successors = te.getChildren("flowsInto", YAWL_NAMESPACE).size();
            if (successors > 1) {
                String splitType = te.getChild("split", YAWL_NAMESPACE)
                        .getAttributeValue("code");
                if (splitType.equals("and")) {
                    metric++;
                }
                else if (splitType.equals("xor")) {
                    metric += successors;
                }
                else if (splitType.equals("or")) {
                    metric += Math.pow(2, successors) - 1;
                }
            }
        }
        return metric;
    }

}