/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Release
* (c) Copyright IBM Corporation 2011, 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.jira;

import java.util.ArrayList;
import java.util.List;

import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.urbancode.commons.webext.util.JSONUtilities;

public class JiraChange {

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

    //**********************************************************************************************
    // INSTANCE
    //**********************************************************************************************
    final private String name;
    final private String description;
    final private String id;
    final private String key;
    final private String typeId;
    final private String typeName;
    final private String statusId;
    final private String statusName;
    final private String priorityId;
    final private String priorityName;
    final private String actualProjectName;
    final private String actualProjectId;
    final private JiraFactory factory;
    
    private String parentLink;
    private String initiativeId;
    private String initiativeName;
    final private List<JiraRelease> releases = new ArrayList<JiraRelease>();
    final private List<JiraComponent> components = new ArrayList<JiraComponent>();
    private List<String> parentLinkKeys = new ArrayList<String>();
    private List<String> childLinkKeys = new ArrayList<String>();
    // Only used for custom field mapping of Application
    private HashMap<String,String> projectNames = new HashMap<String,String>();

    final static private APPLICATION = "Application"
    final static private COMPONENT   = "Components"
    final static private INITIATIVE  = "Initiative"
    final static private RELEASE     = "Release"
    final static private INITIATIVE_NAME = "InitiativeName"
    final static private ISSUE_TYPE = "IssueType"
    final static private ISSUE_LINK_TYPE = "IssueLinkType"
    final static private ISSUE_LINK_DEPTH = "IssueLinkDepth"

    //----------------------------------------------------------------------------------------------
    public JiraChange(JSONObject json, HashMap<String, String> customFields, JiraFactory factory, boolean skipInitiative) throws JSONException {
        this.factory = factory
        JSONObject fields = JSONUtilities.getJsonObjectFromJsonObject(json, "fields");

        name = (JSONUtilities.getStringFromJsonObject(fields, "summary")?:"").trim();
        if (name.length() > 253) {
            name = name.substring(0, 250) + "..."
        }

        description = JSONUtilities.getStringFromJsonObject(fields, "description");
        id = JSONUtilities.getStringFromJsonObject(json, "id");
        key = JSONUtilities.getStringFromJsonObject(json, "key");
        typeId = JSONUtilities.getStringFromJsonObject(
                JSONUtilities.getJsonObjectFromJsonObject(fields, "issuetype"), "id");
        typeName = (JSONUtilities.getStringFromJsonObject(
                JSONUtilities.getJsonObjectFromJsonObject(fields, "issuetype"), "name")?:"").trim();
        statusId = JSONUtilities.getStringFromJsonObject(
                JSONUtilities.getJsonObjectFromJsonObject(fields, "status"), "id");
        statusName = (JSONUtilities.getStringFromJsonObject(
                JSONUtilities.getJsonObjectFromJsonObject(fields, "status"), "name")?:"").trim();
        try {
            priorityId = JSONUtilities.getStringFromJsonObject(
                    JSONUtilities.getJsonObjectFromJsonObject(fields, "priority"), "id");
            priorityName = (JSONUtilities.getStringFromJsonObject(
                JSONUtilities.getJsonObjectFromJsonObject(fields, "priority"), "name")?:"").trim();
        } catch (NullPointerException ex) {
            // Swallow NullPointerException
            // Likely only on JIRA v7
        }


        actualProjectName = (JSONUtilities.getStringFromJsonObject(
                JSONUtilities.getJsonObjectFromJsonObject(fields, "project"), "name")?:"").trim();
        actualProjectId = (JSONUtilities.getStringFromJsonObject(
                JSONUtilities.getJsonObjectFromJsonObject(fields, "project"), "id")?:"").trim();

        // Reecord all Components assigned to this issue
        setCustomValueInit(fields, COMPONENT, "components", key)

        // CUSTOM APPLICATION FIELD

        if (customFields.containsKey(APPLICATION)) {
            setCustomValueInit(fields, APPLICATION, customFields.get(APPLICATION), key)
        }

        // CUSTOM RELEASE FIELD
        // Else Manual Mapping is Used
        if (customFields.containsKey(RELEASE)) {
            setCustomValueInit(fields, RELEASE, customFields.get(RELEASE), key)
        }

        // This will pick up if there is a parent Link by the Parent Link Field, not through the Issue Link
        if (customFields.containsKey(ISSUE_LINK_TYPE)) {
            setCustomValueInit(fields, ISSUE_LINK_TYPE, customFields.get(ISSUE_LINK_TYPE), key)
        }

        //Get Parent Links
        JSONArray links = JSONUtilities.getJsonArrayFromJsonObject(fields, "issuelinks");

        if (links) {
            for(int i=0; i < links.length(); i++) {
                JSONObject link = links.getJSONObject(i)

                if(link.has("inwardIssue")) {
                    this.parentLinkKeys.add(link.getJSONObject("inwardIssue").getString("key"))
                }
                if(link.has("outwardIssue")) {
                    this.childLinkKeys.add(link.getJSONObject("outwardIssue").getString("key"))
                }
            }
        }

        if(!skipInitiative) {
            if(customFields.containsKey(ISSUE_TYPE) && customFields.containsKey(ISSUE_LINK_TYPE)) {

                def initiativeIdNameResult = null;

                def issueLinkDepth = 2

                try {
                    issueLinkDepth = Integer.parseInt(customFields.get(ISSUE_LINK_DEPTH))
                } catch (Exception e) {
                    println ("Can not parse depth string... Default Value = 2")
                }

                if(links) {
                    if(links.length() > 0) {
                        for(int i=0; i < links.length(); i++) {
                            JSONObject link = links.getJSONObject(i)

                            if(link.has("inwardIssue")) {
                                String parentIssueType = link.getJSONObject("inwardIssue").getJSONObject("fields").getJSONObject("issuetype").getString("name")

                                if(parentIssueType == customFields.get(ISSUE_TYPE)) {
                                    String parentId = link.getJSONObject("inwardIssue").getString("key")
                                    String parentName = link.getJSONObject("inwardIssue").getJSONObject("fields").getString("summary")
                                    this.initiativeName = parentName
                                    this.initiativeId = parentId
                                } else {
                                    String parentId = link.getJSONObject("inwardIssue").getString("key")
                                    
                                    if(parentId != null && parentId != "null" && parentId != "null") {
                                        initiativeIdNameResult = findParentWithIssueType(parentId, customFields.get(ISSUE_TYPE), issueLinkDepth, customFields)
                                    }
                                }
                            }
                        }
                    } else if (this.parentLink) {
                        initiativeIdNameResult = findParentWithIssueType(this.parentLink, customFields.get(ISSUE_TYPE), 2, customFields)
                    }

                    // If we are traversing up to find the initiative, there may be an epic in the middle of the chain
                    if (!this.initiativeId && !this.initiativeName) {
                        if (customFields.containsKey(INITIATIVE)) {
                            setCustomValueInit(fields, INITIATIVE, customFields.get(INITIATIVE), key)

                            String parentId = fields.getString(customFields.get(INITIATIVE))

                            if(parentId != null && parentId != "null" && parentId != "null") {
                                initiativeIdNameResult = findParentWithIssueType(parentId, customFields.get(ISSUE_TYPE), 2, customFields)
                            }
                        }
                    }
                }

                if(initiativeIdNameResult) {
                    this.initiativeId = initiativeIdNameResult.getString("id")
                    this.initiativeName = initiativeIdNameResult.getString("name")
                }
            } else {
                // CUSTOM INITIATIVE FIELD
                if (customFields.containsKey(INITIATIVE)) {
                    setCustomValueInit(fields, INITIATIVE, customFields.get(INITIATIVE), key)
                }

                // CUSTOM INITIATIVE FIELD
                if (customFields.containsKey(INITIATIVE_NAME)) {
                    setCustomValueInit(fields, INITIATIVE_NAME, customFields.get(INITIATIVE_NAME), key)
                }
            }
        }
    }

    private JSONObject findParentWithIssueType(String issueId, String issueType, int depth, HashMap<String, String> customFields) {
        if (depth <= 0) {
            return null
        }

        // Never traverse more than 5 layers... that's just crazy
        if (depth > 5) {
            depth = 5
        }

        List<JiraChange> temp = this.factory.getEpicDetail(issueId, customFields)

        JSONObject result = new JSONObject();

        if(temp.size() > 0) {
            JiraChange parent = temp.get(0)

            if(parent.getTypeName() == issueType) {
                result.put("id", parent.getKey());
                result.put("name", parent.getName());
                return result;
            } else {
                if(parent.getParentLinks() && parent.getParentLinks().size() > 0) {
                    for (int i = 0; i < parent.getParentLinks().size(); i++) {
                        String parentId = parent.getParentLinks().get(i)

                        result = findParentWithIssueType(parentId, issueType, depth - 1, customFields)
                    }
                } else if (parent.getParentLink()) {
                    result = findParentWithIssueType(parent.getParentLink(), issueType, depth - 1, customFields)
                }

                if(result && result.has("id")) {
                    return result;
                }
            }
        }
        return null
    }

    //----------------------------------------------------------------------------------------------
    public String getName() {
        return name;
    }

    //----------------------------------------------------------------------------------------------
    public String getDescription() {
        return description;
    }

    //----------------------------------------------------------------------------------------------
    public String getId() {
        return id;
    }

    //----------------------------------------------------------------------------------------------
    public String getKey() {
        return key;
    }

    //----------------------------------------------------------------------------------------------
    public String getTypeId() {
        return typeId;
    }

    //----------------------------------------------------------------------------------------------
    public String getTypeName() {
        return typeName;
    }

    //----------------------------------------------------------------------------------------------
    public String getStatusId() {
        return statusId;
    }

    //----------------------------------------------------------------------------------------------
    public String getStatusName() {
        return statusName;
    }

    //----------------------------------------------------------------------------------------------
    public String getPriorityId() {
        return priorityId;
    }

    //----------------------------------------------------------------------------------------------
    public String getPriorityName() {
        return priorityName;
    }

    //----------------------------------------------------------------------------------------------
    public String getInitiativeName() {
        return initiativeName;
    }

    //----------------------------------------------------------------------------------------------
    public String getInitiativeId() {
        return initiativeId;
    }

    //----------------------------------------------------------------------------------------------
    public String getActualProjectId() {
        return actualProjectId;
    }

    //----------------------------------------------------------------------------------------------
    public String getParentLink() {
        return parentLink;
    }


    //----------------------------------------------------------------------------------------------
    public String getActualProjectName() {
        return actualProjectName;
    }

    //----------------------------------------------------------------------------------------------
    public List<JiraRelease> getReleases() {
        return releases;
    }

    //----------------------------------------------------------------------------------------------
    public List<JiraComponent> getComponents() {
        return components;
    }

    //----------------------------------------------------------------------------------------------
    public List<String> getParentLinks() {
        return parentLinkKeys;
    }

    //----------------------------------------------------------------------------------------------
    public List<String> getChildLinks() {
        return childLinkKeys;
    }

    //----------------------------------------------------------------------------------------------
    public HashMap<String,String> getProjectNames() {
        return projectNames;
    }

    //----------------------------------------------------------------------------------------------
    /**
     * param fields JSONObject containing JIRA Issue Data
     * param customFields HashMap of customField ID and mapped uRelease field
     * @returns Custom Field Release ID and Name are Set
     */
    private void setCustomValueInit(JSONObject fields, String customFieldType, String customFieldId, String key) {
        def customFieldValue = JSONUtilities.getStringFromJsonObject(fields, customFieldId)
        // If Custom Field is null, ignore
        if (customFieldValue) {
            // Multiple Releases in JSONArray
            if (customFieldValue.startsWith("[") && customFieldValue.endsWith("]") && JSONUtilities.getJsonArrayFromJsonObject(fields, customFieldId) != null) {
                setCustomValue(customFieldType, JSONUtilities.getJsonArrayFromJsonObject(fields, customFieldId))
            }
            // Single Release in JSON
            else if (customFieldValue.startsWith("{")) {
                setCustomValue(customFieldType, JSONUtilities.getJsonObjectFromJsonObject(fields, customFieldId))
            }
            // Single Release in String
            else {
                setCustomValue(customFieldType, customFieldValue)
            }
        } else {
            println "[" + customFieldType + "] Expecting to find the field: " + customFieldId + " in work item, but was not found. (" + key + ")"
        }
    }

    //----------------------------------------------------------------------------------------------
    private void setCustomValue(String customFieldType, JSONArray jsonArray) {
        for (int i = 0; i < jsonArray.length(); i++) {
            setCustomValue(customFieldType, jsonArray.getJSONObject(i))
        }
    }

    //----------------------------------------------------------------------------------------------
    private void setCustomValue(String customFieldType, JSONObject jsonObj) {
        def name = JSONUtilities.getStringFromJsonObject(jsonObj, "name")
        // If "name" property doesn't exist, set name as "value" instead
        if (!name) {
            name = JSONUtilities.getStringFromJsonObject(jsonObj, "value")
        }
        def id = JSONUtilities.getStringFromJsonObject(jsonObj, "id")

        setCustomValue(customFieldType, name + ":" + id, jsonObj)
    }

    //----------------------------------------------------------------------------------------------
    private void setCustomValue(String customFieldType, String jsonStr, JSONObject originalObj) {
        // Delimit the String by a _ or :
        int colonIndex = jsonStr.lastIndexOf(":")
        int underscoreIndex = jsonStr.lastIndexOf("_")
        int i = colonIndex > underscoreIndex ? colonIndex : underscoreIndex

        // Construct name and id strings, default to jsonStr
        String name = jsonStr
        String id = jsonStr
        if (i > -1) {
            name = jsonStr.substring(0,i).trim()
            id = jsonStr.substring(i+1).trim()
        }

        if(customFieldType == APPLICATION) {
            this.projectNames.put(id, name)
        }
        else if(customFieldType == INITIATIVE) {
            this.initiativeName = name
            this.initiativeId = id
        }
        else if(customFieldType == INITIATIVE_NAME) {
            this.initiativeName = name
        }
        else if (customFieldType == RELEASE) {
            JSONObject obj = new JSONObject()
            obj.put("name", name)
            obj.put("id", id)
            if(originalObj) {
                obj.put("releaseDate", JSONUtilities.getStringFromJsonObject(originalObj, "releaseDate"))
            }

            releases.add(new JiraRelease(obj))
        }
        else if (customFieldType == COMPONENT) {
            JSONObject obj = new JSONObject()
            obj.put("name", name)
            obj.put("id", id)
            components.add(new JiraComponent(obj))
        }
        else if (customFieldType == ISSUE_LINK_TYPE) {
            this.parentLink = jsonStr
        }
        else {
            throw new Exception("[Error] Unknown ${customFieldType}' customFieldType reached in setCustomValue.")
        }
    }

    //----------------------------------------------------------------------------------------------
    private void setCustomValue(String customFieldType, String jsonStr) {
        setCustomValue(customFieldType, jsonStr, null)
    }
}
