
package org.openrefine.wikibase.functions;

import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;

import com.google.refine.ProjectManager;
import com.google.refine.expr.EvalError;
import com.google.refine.expr.WrappedRow;
import com.google.refine.grel.ControlFunctionRegistry;
import com.google.refine.grel.Function;
import com.google.refine.model.ColumnModel;
import com.google.refine.model.OverlayModel;
import com.google.refine.model.Project;
import com.google.refine.model.Row;

import org.openrefine.wikibase.manifests.Manifest;
import org.openrefine.wikibase.manifests.ManifestException;
import org.openrefine.wikibase.manifests.ManifestParser;
import org.openrefine.wikibase.qa.EditInspector;
import org.openrefine.wikibase.qa.QAWarning;
import org.openrefine.wikibase.qa.QAWarningStore;
import org.openrefine.wikibase.schema.ExpressionContext;
import org.openrefine.wikibase.schema.WikibaseSchema;
import org.openrefine.wikibase.schema.exceptions.QAWarningException;
import org.openrefine.wikibase.updates.EntityEdit;

/**
 * GREL function which computes the "aggregation identifiers" of the issues found on the edits generated by a given row.
 * Those "aggregation identifiers" are used to group issues together (increasing their number of occurrences and
 * reporting them only once to the user).
 * 
 * This GREL function is primarily meant to be used in facets, to select rows which generate a particular issue, with
 * expressions of the form: grel:wikibaseIssues().inArray('duplicate-whitespace')
 * 
 * @author Antonin Delpeuch
 *
 */
public class WikibaseIssuesFunction implements Function {

    @Override
    public Object call(Properties bindings, Object[] args) {
        if (args.length != 0) {
            return new EvalError(ControlFunctionRegistry.getFunctionName(this) + "() does not expect any arguments");
        }

        Project project = (Project) bindings.get("project");

        // Fetch schema
        OverlayModel overlayModel = project.overlayModels.get("wikibaseSchema");
        if (overlayModel == null || !(overlayModel instanceof WikibaseSchema)) {
            return new EvalError("No wikibase schema associated with this project");
        }
        WikibaseSchema schema = (WikibaseSchema) overlayModel;

        // Fetch manifest
        // TODO cache this step as we probably do not want to be parsing the preferences at every function call
        Manifest manifest = getManifest(schema.getMediaWikiApiEndpoint());
        if (manifest == null) {
            return new EvalError("No Wikibase manifest found for MediaWiki API URL " + schema.getMediaWikiApiEndpoint());
        }

        QAWarningStore warningStore = new QAWarningStore();
        int rowId = (int) bindings.get("rowIndex");
        Row row = ((WrappedRow) bindings.get("row")).row;
        ColumnModel columnModel = project.columnModel;
        ExpressionContext expressionContext = new ExpressionContext(
                schema.getSiteIri(),
                schema.getEntityTypeSiteIri(),
                schema.getMediaWikiApiEndpoint(),
                rowId,
                row,
                columnModel,
                warningStore);

        // Evaluate the schema
        try {
            List<EntityEdit> entityEdits = schema.evaluateEntityDocuments(expressionContext);
            EditInspector inspector = new EditInspector(warningStore, manifest, false);
            inspector.inspect(entityEdits, schema);

            return warningStore.getWarnings()
                    .stream()
                    .map(QAWarning::getAggregationId)
                    .distinct()
                    .collect(Collectors.toList());
        } catch (QAWarningException e) {
            // if the evaluation prematurely returned a QAWarning, return this single warning as result
            return Collections.singletonList(e.getWarning().getAggregationId());
        } catch (ExecutionException e) {
            return new EvalError("Executing the quality checks failed: " + e.getMessage());
        }
    }

    @Override
    public String getDescription() {
        return "function taking no arguments and computing the Wikibase quality issues for the current row";
    }

    @Override
    public String getReturns() {
        return "returns the list of Wikibase issue aggregation identifiers for the edits generated by the current row";
    }

    protected Manifest getManifest(String mediaWikiApiEndpoint) {
        Object manifests = ProjectManager.singleton.getPreferenceStore().get("wikibase.manifests");
        if (manifests == null || !(manifests instanceof ArrayNode)) {
            return null;
        }
        for (JsonNode manifest : (ArrayNode) manifests) {
            try {
                Manifest parsed = ManifestParser.parse(manifest);
                if (mediaWikiApiEndpoint.equals(parsed.getMediaWikiApiEndpoint())) {
                    return parsed;
                }
            } catch (ManifestException e) {
                // skip invalid manifests in this context
            }
        }
        return null;
    }

}
