/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* IBM AnthillPro
* (c) Copyright IBM Corporation 2002, 2017. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*/
package com.urbancode.air.plugin.integration

import java.util.ArrayList;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity
import org.apache.http.util.EntityUtils


import com.urbancode.air.AirPluginTool
import com.urbancode.air.plugin.rtcworkitems.RTCWorkItemHelper;
import com.urbancode.air.XTrustProvider;
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder;


public class ChangeDefectStatus {

    //*****************************************************************************************************************
    // CLASS
    //*****************************************************************************************************************

    //*****************************************************************************************************************
    // INSTANCE
    //*****************************************************************************************************************

    String url
    String password
    String username
    String projectName
    String action
    String newState
    String workItemId

    def allTypes
    def etag
    def props

    public boolean execute() {
        HttpClientBuilder builder = new HttpClientBuilder();
        builder.setTrustAllCerts(true);
        HttpClient client = builder.buildClient();

        allTypes = new HashMap<String,Type>();

        if (!action && !newState) {
            throw new Exception("Must enter either an Action or State.");
        }

        XTrustProvider.install();
        def rtcHelper = new RTCWorkItemHelper(props);
        def catalogurl = rtcHelper.getCatalogUrl(client);
        rtcHelper.authenticateRTCUser(catalogurl, client);
        def servicesUrl = rtcHelper.getServicesUrl(catalogurl, client);
        def workItemUrl = rtcHelper.getWorkItemUrl(servicesUrl, client);

        if (action) {
            System.out.println("Applying action $action to work item $workItemId.");
        }
        else {
            System.out.println("Changing work item $workItemId to state $newState.");
        }
        System.out.println();

        Type type;
        State state;
        boolean updated = false;

        def workItemXml = rtcHelper.getWorkItemByIdentifier(workItemId, workItemUrl, client, etag);
        if (!workItemXml) {
            println "Unable to find work item $workItemId";
            println ""
            return updated // quit early
        }
        else {
            System.out.println("Attempting to update work item ${workItemId}.");
        }


        String currWorkItemUrl
        try {
            currWorkItemUrl = workItemXml[RTCWorkItemHelper.n_oslc_cm.ChangeRequest][0].attributes().toMapString() - "[{${RTCWorkItemHelper.n_rdf.uri}}resource:" - "]"
        } catch (NullPointerException ex) {
            println "[Error] Work Item ${workItemId} was not found."
            return updated
        }

        type = getCurrentWorkItemType(workItemXml, client);
        state = getCurrentWorkItemState(workItemXml, type.getWorkflow());

        Workflow workflow = type.getWorkflow();
        String stateName = state.getName();
        ArrayList<Action> requiredActions;

        println ""

        println "The valid actions for Work Item $workItemId are: "
        workflow.getActions().each { it ->
        println it.getName()
        }

        println ""

        println "The valid states for Work Item $workItemId are: "
        workflow.getStates().each{ it ->
            println it.getName()
        }

        println ""

        if (action) {
            if (workflow.hasAction(action)) {
                requiredActions = workflow.getAction(action); // there should only ever be one
                Action act = requiredActions.get(0);
                if (!stateName.equals(act.getResultingStateName())) {
                    updated = updateItem(currWorkItemUrl, client, requiredActions, workflow, state);
                }
                else {
                    System.out.println("Work item ${workItemId} is already in the state $stateName, "
                                     + "which would be the result of action " + act.getName() + ".");
                    updated = true;
                }
            }
            else {
                println "The action $action is not applicable for work item $workItemId."
            }
        }
        else {
            if (workflow.hasState(newState)) {
                requiredActions = workflow.getRequiredAction(newState);
                if (!stateName.equals(requiredActions.get(0).getResultingStateName())) {  // because each's resulting state is the same
                    updated = updateItem(currWorkItemUrl, client, requiredActions, workflow, state);
                }
                else {
                    System.out.println("Work item ${workItemId} is already in the state $stateName.");
                    updated = true;
                }
            }
            else {
                println "The state $newState is not applicable for work item $workItemId."
            }
        }

        if (updated) {
            System.out.println("Update was successful.");
        }
        else {
            println ("Update was not successful.")
        }
        System.out.println();
        return updated
    }

    //-----------------------------------------------------------------------------------------------------------------
    def final Type getCurrentWorkItemType(def workItemXml, def client) {
        Type currentType = null;

        def typeUrl = workItemXml[RTCWorkItemHelper.n_oslc_cm.ChangeRequest][0][RTCWorkItemHelper.n_dc.type][0].attributes().toMapString() - "[{${RTCWorkItemHelper.n_rdf.uri}}resource:" - "]"
        String typeID = typeUrl.substring(typeUrl.lastIndexOf('/') + 1);
        if (allTypes.containsKey(typeID)) {
            currentType = allTypes.get(typeID);
        }
        else {
            def typeXml = queryUrl(typeUrl, client);
            String name = typeXml[RTCWorkItemHelper.n_dc.title].text();

            currentType = addNewType(new Type(typeID), workItemXml, client);
            currentType.setName(name);
            allTypes.put(typeID, currentType);
        }

        return currentType;
    }

    //-----------------------------------------------------------------------------------------------------------------
    final def addNewType(Type type, def workItemXml, def client) {
        def currentStateUrl = workItemXml[RTCWorkItemHelper.n_oslc_cm.ChangeRequest][0][RTCWorkItemHelper.n_rtc_cm.state][0].attributes().toMapString() - "[{${RTCWorkItemHelper.n_rdf.uri}}resource:" - "]"
        String temp = currentStateUrl.substring(0, currentStateUrl.lastIndexOf('/'));
        String workflowID = temp.substring(0, temp.lastIndexOf('/'));
        String currentStateID = currentStateUrl.substring(currentStateUrl.lastIndexOf('/') + 1);

        def statesUrl = currentStateUrl.substring(0, currentStateUrl.lastIndexOf('/'));

        /*
         * The actions URL is the same as the states URL but with "actions"
         * replacing "states" in the URL path.
         */
        def actionsUrl = new URI(statesUrl)
        actionsUrl = new URI(
            actionsUrl.getScheme(),
            actionsUrl.getAuthority(),
            actionsUrl.getPath()?.replace('states', 'actions'),
            actionsUrl.getQuery(),
            actionsUrl.getFragment()
        ).toString()

        def statesXml = queryUrl(statesUrl, client);
        def actionsXml = queryUrl(actionsUrl, client);

        ArrayList<State> states = new ArrayList<State>();
        Map<String,State> statesMap = new HashMap<String,State>();
        State newState;
        statesXml[RTCWorkItemHelper.n_rtc_cm.Status].each { state ->
            String id = state[RTCWorkItemHelper.n_dc.identifier].text();
            String name =state[RTCWorkItemHelper.n_dc.title].text();
            newState = new State(id, name);
            states.add(newState);
            statesMap.put(id, newState);
        }

        ArrayList<Action> actions = new ArrayList<Action>();
        Action newAction;
        actionsXml[RTCWorkItemHelper.n_rtc_cm.Action].each { action ->
            String name = action[RTCWorkItemHelper.n_dc.title].text();
            String command = action[RTCWorkItemHelper.n_dc.identifier].text();
            String resultingState = action[RTCWorkItemHelper.n_rtc_cm.resultState][0].attributes().toMapString() - "[{${RTCWorkItemHelper.n_rdf.uri}}resource:" - "]"
            String resultingStateID = resultingState.substring(resultingState.lastIndexOf('/') + 1);
            newAction = new Action(name, command, statesMap.get(resultingStateID).getName());
            actions.add(newAction);
        }

        Workflow workflow = new Workflow(workflowID, states, actions);
        type.setWorkflow(workflow);

        return type;
    }

    //-----------------------------------------------------------------------------------------------------------------
    def final queryUrl(String someUrl, HttpClient client) {
        String respStr = "";
        def resp = "";
        HttpRequestBase queryMethod = null;

        try {
            queryMethod = new HttpGet(someUrl);
            queryMethod.setHeader("Accept", "application/x-oslc-cm-change-request+xml");
            def result = client.execute(queryMethod);
            respStr = result.getEntity().getContent().getText("UTF-8");
        }
        finally {
            if (queryMethod != null) {
                queryMethod.releaseConnection();
            }
        }
        return parseToXml(respStr);
    }

    //-----------------------------------------------------------------------------------------------------------------
    def final parseToXml(String rtcRep) {
        def workItemXml = null;
        try {
            workItemXml = new XmlParser().parseText(rtcRep);
        }
        catch (Exception e) {
            System.out.println(rtcRep);
            throw e;
        }
        return workItemXml;
    }

    //-----------------------------------------------------------------------------------------------------------------
    def final State getCurrentWorkItemState(def workItemXml, Workflow workflow) {
        State state;
        String stateUrl = workItemXml[RTCWorkItemHelper.n_oslc_cm.ChangeRequest][0][RTCWorkItemHelper.n_rtc_cm.state][0].attributes().toMapString() - "[{${RTCWorkItemHelper.n_rdf.uri}}resource:" - "]"
        String stateID = stateUrl.substring(stateUrl.lastIndexOf('/') + 1);
        state = workflow.getStateByID(stateID);
        return state;
    }

    //-----------------------------------------------------------------------------------------------------------------
    def final boolean updateItem(createUrl, HttpClient client, ArrayList<Action> actions, Workflow workflow, State currentState) {
        boolean successful = false;
        HttpPut changeStatusMethod = null;

        try {
            for (Action actn : actions) {
                String command = actn.getCommand();

                def uriBuilder = new org.apache.http.client.utils.URIBuilder(createUrl);
                uriBuilder.addParameter("_action", command);
                println uriBuilder.build()

                changeStatusMethod = new HttpPut(uriBuilder.build());
                changeStatusMethod.setHeader("Accept", "application/x-oslc-cm-change-request+xml");
                changeStatusMethod.setHeader("Content-Type", "application/x-oslc-cm-change-request+json");
                changeStatusMethod.setHeader("ETag", etag)
                changeStatusMethod.setEntity(new StringEntity("{}"));

                def response = client.execute(changeStatusMethod);
                def status = response.getStatusLine().getStatusCode();
                def responseEntity = response.getEntity()
                def resp = responseEntity.getContent();
                String respStr = resp.getText("UTF-8");

                if (status < 300 && status >= 200) {
                    def workItemXml = parseToXml(respStr);

                    if (action) {
                        if (stateChanged(workItemXml, workflow, currentState)) {
                            successful = true;
                            println("Query URL = " + changeStatusMethod.getURI());
                        }
                    }
                    else {
                        if (stateChanged(workItemXml, workflow, null)) {
                            successful = true;
                            println("Query URL = " + changeStatusMethod.getURI());
                            break;
                        }
                    }
                }
                else {
                    System.out.println("Update failed with result $status.");
                }
            }
        }
        finally {
            if (changeStatusMethod != null) {
                changeStatusMethod.releaseConnection();
            }
            if (!successful) {
                String currentStateName = currentState.getName();
                if (action) {
                    System.out.println("The action $action could not be performed while in the state $currentStateName.");
                }
                else {
                    System.out.println("The state $newState could not be reached from state $currentStateName.");
                }
            }
        }
        return successful;
    }

    //-----------------------------------------------------------------------------------------------------------------
    def final boolean stateChanged(def workItemXml, Workflow workflow, State currentState) {
        boolean change = false;
        String stateUrl = workItemXml[RTCWorkItemHelper.n_rtc_cm.state][0].attributes().toMapString() - "[{${RTCWorkItemHelper.n_rdf.uri}}resource:" - "]"
        String stateID = stateUrl.substring(stateUrl.lastIndexOf('/') + 1);
        State state = workflow.getStateByID(stateID);
        if (currentState) {
            if (!currentState.getName().equals(state.getName())) {
                change = true;
            }
        }
        else {
            if (newState.equals(state.getName())) {
                change = true;
            }
        }

        return change;
    }

}

//*********************************************************************************************************************
public class Type {
    String id;
    String name;
    Workflow workflow;

    Type(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Workflow getWorkflow() {
        return workflow;
    }

    public void setWorkflow(Workflow workflow) {
        this.workflow = workflow;
    }
}

//*********************************************************************************************************************
public class Workflow {
    String id;
    ArrayList<State> states;
    ArrayList<Action> actions;

    Workflow(String id, ArrayList<State> states, ArrayList<Action> actions) {
        this.id = id;
        this.states = states;
        this.actions = actions;
    }

    public String getId() {
        return id;
    }

    public ArrayList<State> getStates() {
        return states;
    }

    public void addState(State state) {
        if (states == null) {
            states = new ArrayList<State>();
        }
        states.add(state);
    }

    public void addStates(ArrayList<State> states) {
        this.states = states;
    }

    public State getStateByID(String stateID) {
        for (State state : states) {
            if (stateID.equals(state.getId())) {
                return state;
            }
        }
        return null;
    }

    public State getStateByName(String stateName) {
        for (State state : states) {
            if (stateName.equals(state.getName())) {
                return state;
            }
        }
        return null;
    }

    public boolean hasState(String stateName) {
        for (State state : states) {
            if (stateName.equals(state.getName())) {
                return true;
            }
        }
        return false;
    }

    public ArrayList<Action> getActions() {
        return actions;
    }

    public void addAction(Action action) {
        if (actions == null) {
            actions = new ArrayList<Action>();
        }
        actions.add(action);
    }

    public void addActions(ArrayList<Action> actions) {
        this.actions = actions;
    }

    public ArrayList<Action> getAction(String actionName) {
        ArrayList<Action> theAction = new ArrayList<Action>();
        for (Action action : actions) {
            if (actionName.equals(action.getName())) {
                theAction.add(action);
                break;
            }
        }
        return theAction;
    }

    public ArrayList<Action> getRequiredAction(String stateName) {
        ArrayList<Action> possibleActions = new ArrayList<Action>();
        for (Action action : actions) {
            if (stateName.equals(action.getResultingState())) {
                possibleActions.add(action);
            }
        }
        return possibleActions;
    }

    public boolean hasAction(String actionName) {
        for (Action action : actions) {
            if (actionName.equals(action.getName())) {
                return true;
            }
        }
        return false;
    }
}

//*********************************************************************************************************************
public class State {
    String id;
    String name;

    State(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

}

//*********************************************************************************************************************
public class Action {
    String name;
    String command;
    String resultingStateName;

    Action(String name, String command, String resultingStateName) {
        this.name = name;
        this.command = command;
        this.resultingStateName = resultingStateName;
    }

    public String getCommand() {
        return command;
    }

    public String getName() {
        return name;
    }

    public String getResultingState() {
        return resultingStateName;
    }

}
