/*
 * Copyright 2017 HugeGraph Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this
 * work for additional information regarding copyright ownership. The ASF
 * licenses this file to You 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.
 */

package com.baidu.hugegraph.api.task;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.baidu.hugegraph.api.API;
import com.baidu.hugegraph.client.RestClient;
import com.baidu.hugegraph.rest.ClientException;
import com.baidu.hugegraph.rest.RestResult;
import com.baidu.hugegraph.structure.Task;
import com.baidu.hugegraph.structure.constant.HugeType;
import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.TaskCache;
import com.google.common.collect.ImmutableMap;

public class TaskAPI extends API {

    private static final String PATH = "graphs/%s/tasks";
    private String graph;
    public static final String TASKS = "tasks";
    public static final String TASK_ID = "task_id";
    public static final long TASK_TIMEOUT = 60L;
    private static final long QUERY_INTERVAL = 500L;

    public TaskAPI(RestClient client, String graph) {
        super(client);
        this.path(String.format(PATH, graph));
        this.graph = graph;
    }

    @Override
    protected String type() {
        return HugeType.TASK.string();
    }

    public String graph() {
        return this.graph;
    }

    public List<Task> list(String status, long limit) {
        Map<String, Object> params = new LinkedHashMap<>();
        params.put("limit", limit);
        if (status != null) {
            params.put("status", status);
        }
        RestResult result = this.client.get(this.path(), params);
        return result.readList(TASKS, Task.class);
    }

    public TasksWithPage list(String status, String page, long limit) {
        E.checkArgument(page != null, "The page can not be null");
        this.client.checkApiVersion("0.48", "getting tasks by paging");
        Map<String, Object> params = new LinkedHashMap<>();
        params.put("limit", limit);
        params.put("page", page);
        if (status != null) {
            params.put("status", status);
        }
        RestResult result = this.client.get(this.path(), params);
        return result.readObject(TasksWithPage.class);
    }

    public List<Task> list(List<Long> ids) {
        Map<String, Object> params = new LinkedHashMap<>();
        params.put("ids", ids);
        RestResult result = this.client.get(this.path(), params);
        return result.readList(TASKS, Task.class);
    }

    public Task get(long id) {
        RestResult result = this.client.get(this.path(), String.valueOf(id));
        return result.readObject(Task.class);
    }

    public void delete(long id) {
        this.client.delete(path(), String.valueOf(id));
    }

    public Task cancel(long id) {
        Map<String, Object> params = ImmutableMap.of("action", "cancel");
        RestResult result = this.client.put(path(), String.valueOf(id),
                                            ImmutableMap.of(), params);
        return result.readObject(Task.class);
    }

    public Task waitUntilTaskSuccess(long taskId, long seconds) {
        long passes = seconds * 1000 / QUERY_INTERVAL;
        try {
            for (long pass = 0; ; pass++) {
                Task task = this.getFromCache(taskId);
                if (task.success()) {
                    return task;
                } else if (task.completed()) {
                    throw new ClientException(
                              "Task '%s' is %s, result is '%s'",
                              taskId, task.status(), task.result());
                }
                if (pass >= passes) {
                    break;
                }
                try {
                    // Query every half second from cache to decrease waiting
                    // time because restful query is executed per second
                    Thread.sleep(QUERY_INTERVAL);
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
            throw new ClientException(
                      "Task '%s' not completed in %s seconds, " +
                      "it can still be queried by task-get API",
                      taskId, seconds);
        } finally {
            // Stop querying this task info whatever
            this.removeFromCache(taskId);
        }
    }

    private Task getFromCache(long taskId) {
        return TaskCache.instance().get(this, taskId);
    }

    private void removeFromCache(long taskId) {
        TaskCache.instance().remove(this, taskId);
    }

    public static long parseTaskId(Map<String, Object> task) {
        E.checkState(task.size() == 1 && task.containsKey(TASK_ID),
                     "Task must be formatted to {\"%s\" : id}, but got %s",
                     TASK_ID, task);
        Object taskId = task.get(TASK_ID);
        E.checkState(taskId instanceof Number,
                     "Task id must be number, but got '%s'", taskId);
        return ((Number) taskId).longValue();
    }
}