/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* (c) Copyright IBM Corporation 2012, 2014. 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.automation

import Action;
import State;
import Type;
import Workflow;

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

import org.apache.commons.httpclient.*
import org.apache.commons.httpclient.methods.*
import org.apache.http.HttpResponse
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPut
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.entity.StringEntity;

import com.urbancode.air.*
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.util.IO
import com.urbancode.ubuild.plugin.rtcworkitems.RTCWorkItemHelper


public class ChangeDefectStatus extends AutomationBase {
    
    //*****************************************************************************************************************
    // CLASS
    //*****************************************************************************************************************
    
    //*****************************************************************************************************************
    // INSTANCE
    //*****************************************************************************************************************
    
    def ids
    String action
    String newState
    ArrayList<String> defectTypes
    
    def etag
    Map<String,Type> allTypes;
    
    public void execute(props, apTool) {
        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, apTool);
        rtcHelper.authenticateRTCUser(client);
        def catalogUrl = rtcHelper.getCatalogUrl(client);
        def servicesUrl = rtcHelper.getServicesUrl(catalogUrl, client);
        def workItemUrl = rtcHelper.getWorkItemUrl(servicesUrl, client);
        System.out.println();
        
        if (action) {
            System.out.println("Applying action $action to " + (defectTypes ? "$defectTypes " : "") + "work items $ids.");
        }
        else {
            System.out.println("Changing " + (defectTypes ? "$defectTypes " : "") + "work items $ids to state $newState.");
        }
        System.out.println();
        
        ids.each { workItem ->
            Type type;
            State state;
            boolean updated = false;
            
            def workItemXml = getWorkItemByIdentifier(workItem, workItemUrl, client);
            if (!workItemXml) {
                println "Unable to find work item $workItem";
                System.out.println();
                return;
            }
            else {
                System.out.println("Attempting to update work item ${workItem}.");
            }
            
            String currWorkItemUrl = url;
            if (!url.endsWith('/')) {
                currWorkItemUrl += '/'
            }
            currWorkItemUrl += 'resource/itemName/com.ibm.team.workitem.WorkItem/' + workItem;    // probably a future bug!
            println("Current Work Item URL = " + currWorkItemUrl);
            
            type = getCurrentWorkItemType(workItemXml, client);
            state = getCurrentWorkItemState(workItemXml, type.getWorkflow());
            
            String typeName = type.getName();
            if (!defectTypes || defectTypes.contains(typeName)) {
                Workflow workflow = type.getWorkflow();
                String stateName = state.getName();
                ArrayList<Action> requiredActions;
                
                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 ${workItem} is already in the state $stateName, "
                                             + "which would be the result of action " + act.getName() + ".");
                        }
                    }
                    else {
                        throw new Exception("The type $typeName does not have action $action.");
                    }
                }
                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 ${workItem} is already in the state $stateName.");
                        }
                    }
                    else {
                        throw new Exception("The type $typeName does not have the state $newState.");
                    }
                }
            }
            else {
                System.out.println("Work item ${workItem} was skipped because it is a ${typeName}.");
            }
            
            if (updated) {
                System.out.println("Update was successful.");
            }
            System.out.println();
        }
    }
    
    //-----------------------------------------------------------------------------------------------------------------
    def final Type getCurrentWorkItemType(def workItemXml, def client) {
        Type currentType = null;
        
        def typeUrl = workItemXml[RTCWorkItemHelper.n_oslc_cm.ChangeRequest][RTCWorkItemHelper.n_dc.type][0].attributes()[RTCWorkItemHelper.n_rdf.'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][RTCWorkItemHelper.n_rtc_cm.state][0].attributes()[RTCWorkItemHelper.n_rdf.'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('/'));
        def actionsUrl = statesUrl.replace("states", "actions");
        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()[RTCWorkItemHelper.n_rdf.'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 = "";
        HttpGet queryMethod = null;
        
        try {
            queryMethod = new HttpGet(someUrl);
            queryMethod.addHeader("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][RTCWorkItemHelper.n_rtc_cm.state][0].attributes()[RTCWorkItemHelper.n_rdf.'resource'];
        String stateID = stateUrl.substring(stateUrl.lastIndexOf('/') + 1);
        state = workflow.getStateByID(stateID);
        return state;
    }
    
    //-----------------------------------------------------------------------------------------------------------------
    def final getWorkItemByIdentifier(String identifier, String queryUrl, HttpClient client) {
        String respStr = "";
        HttpGet queryMethod = null;
        
        try {
            queryMethod = new HttpGet(queryUrl + '.xml?oslc_cm.query=' + URLEncoder.encode('dc:identifier="' + identifier + '"', "UTF-8"));
            queryMethod.addHeader("Accept", "application/x-oslc-cm-change-request+xml");
            def response = client.execute(queryMethod);
            etag = response.getFirstHeader("ETag").getValue();
            respStr = response.getEntity().getContent().getText("UTF-8");
        }
        finally {
            if (queryMethod != null) {
                queryMethod.releaseConnection();
            }
        }
        return parseToXml(respStr);
    }
    
    //-----------------------------------------------------------------------------------------------------------------
    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);
                
                changeStatusMethod = new HttpPut(uriBuilder.build());
                changeStatusMethod.addHeader("Accept", "application/x-oslc-cm-change-request+xml");
                changeStatusMethod.addHeader("Content-Type", "application/x-www-form-urlencoded");
                changeStatusMethod.addHeader("ETag", etag)
//                changeStatusMethod.setEntity(new StringEntity(""));
                
                HttpResponse response = client.execute(changeStatusMethod);
                def responseCode = response.statusLine.statusCode;
                def resp = IO.readText(response.entity.content);
                
                if (isGoodResponseCode(responseCode)) {
                    def workItemXml = parseToXml(resp);
                    
                    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 $response.");
                    println("Query URL = " + changeStatusMethod.getURI());
                    println resp;
                }
            }
        }
        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()[RTCWorkItemHelper.n_rdf.'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;
    }
    
    //-----------------------------------------------------------------------------------------------------------------
    private boolean isGoodResponseCode(int responseCode) {
        return responseCode >= 200 && responseCode < 300;
    }
    
}

//*********************************************************************************************************************
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;
    }
    
}
