package com.atlassian.confluence.extra.jira;

import bucket.cache.CacheManager;
import bucket.container.ContainerManager;
import bucket.util.FileUtils;
import com.atlassian.confluence.renderer.radeox.macros.MacroUtils;
import com.atlassian.confluence.renderer.radeox.macros.include.AbstractHttpRetrievalMacro;
import com.atlassian.confluence.security.GateKeeper;
import com.atlassian.confluence.setup.BootstrapManager;
import com.atlassian.confluence.util.JiraIconMappingManager;
import com.atlassian.confluence.util.GeneralUtil;
import com.atlassian.confluence.util.velocity.VelocityUtils;
import com.atlassian.confluence.util.io.IOUtils;
import com.atlassian.user.User;
import com.atlassian.user.impl.cache.Cache;
import com.opensymphony.util.TextUtils;
import com.opensymphony.webwork.ServletActionContext;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.log4j.Category;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;
import org.radeox.api.engine.context.InitialRenderContext;
import org.radeox.macro.parameter.MacroParameter;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.*;

/**
 * A macro to import/fetch JIRA issues...
 */
public class JiraIssuesMacro extends AbstractHttpRetrievalMacro
{
    private Category log = Category.getInstance(getClass());

    private static final String MACRO_REFRESH = "macro.refresh";
    private String[] myParamDescription = new String[]{"1: url", "?2: columns"};
    private List defaultColumns = new LinkedList();
    private CacheManager cacheManager;
    private BootstrapManager bootstrapManager;
    private GateKeeper gateKeeper;

    public void setInitialContext(InitialRenderContext initialRenderContext)
    {
        super.setInitialContext(initialRenderContext);

        defaultColumns.clear();
        defaultColumns.add("type");
        defaultColumns.add("key");
        defaultColumns.add("summary");
        defaultColumns.add("assignee");
        defaultColumns.add("reporter");
        defaultColumns.add("priority");
        defaultColumns.add("status");
        defaultColumns.add("resolution");
        defaultColumns.add("created");
        defaultColumns.add("updated");
        defaultColumns.add("due");
    }

    public String getName()
    {
        return "jiraissues";
    }

    public String[] getParamDescription()
    {
        return myParamDescription;
    }

    public String getHtml(MacroParameter macroParameter) throws IllegalArgumentException, IOException
    {
        String url = TextUtils.noNull(macroParameter.get("url", 0)).trim();
        url = cleanUrlParentheses(url);
        String columns = TextUtils.noNull(macroParameter.get("columns", 1)).trim();

        String count = TextUtils.noNull(macroParameter.get("count")).trim();

        //is the cache being used?
        String cacheStr = TextUtils.noNull(macroParameter.get("cache", 2)).trim();

        if (cacheStr.equals("")) //no arg. supplied, so we default it to "true"
            cacheStr = "true";

        boolean flush = !Boolean.valueOf(cacheStr).booleanValue();

        // if flushCacheParameterSet is false, check whether flush cache is set via urlparameter
        if (!flush)
        {
            flush = flushCacheParameterSet();
        }

        String refreshUrl = getRefreshUrl();


        Map contextMap = MacroUtils.defaultVelocityContext();
        Element channel = fetchChannel(url, flush);

        // If we couldn't retrieve a channel return an error
        if (channel == null)
            return "error";

        String clickableUrl = makeClickableUrl(url);
        if (TextUtils.stringSet(macroParameter.get("baseurl")))
            clickableUrl = rebaseUrl(clickableUrl, macroParameter.get("baseurl").trim());

        if ("true".equals(count))
            return countHtml(clickableUrl, channel);

        contextMap.put("url", url);
        contextMap.put("clickableUrl", clickableUrl);
        contextMap.put("channel", channel);
        contextMap.put("entries", channel.getChildren("item"));
        contextMap.put("columns", prepareDisplayColumns(columns));
        contextMap.put("icons", prepareIconMap(channel));
        contextMap.put("refreshUrl", refreshUrl);

        return VelocityUtils.getRenderedTemplate("templates/extra/jira/jiraissues.vm", contextMap);
    }

    public String rebaseUrl(String clickableUrl, String baseUrl)
    {
        return clickableUrl.replaceFirst(
                "^" + // only at start of string
                        ".*?" + // minimum number of characters (the schema) followed by...
                        "://" + // literally: colon-slash-slash
                        "[^/]+", // one or more non-slash characters (the hostname)
                baseUrl);
    }

    private String countHtml(String url, Element channel)
    {
        return "<a href=\"" + url + "\">" + channel.getChildren("item").size() + " issues</a>";
    }

    private String makeClickableUrl
            (String
                    url)
    {
        String link = url;

        if (link.indexOf("view=rss") > 0)
            link = link.replaceAll("view=rss", "");

        if (link.indexOf("decorator=none") > 0)
            link = link.replaceAll("decorator=none", "");

        int usernameIdx = link.indexOf("&os_username=");
        if (usernameIdx > 0)
        {
            int nextAmp = link.indexOf('&', usernameIdx + 1);

            if (nextAmp > 0)
                link = link.substring(0, usernameIdx) + link.substring(nextAmp);
            else
                link = link.substring(0, usernameIdx);
        }

        int passwordIdx = link.indexOf("&os_password=");
        if (passwordIdx > 0)
        {
            int nextAmp = link.indexOf('&', passwordIdx + 1);

            if (nextAmp > 0)
                link = link.substring(0, passwordIdx) + link.substring(nextAmp);
            else
                link = link.substring(0, passwordIdx);
        }
        return link;
    }

    private List prepareDisplayColumns
            (String
                    columns)
    {
        if (columns == null || columns.equals(""))
        { // No "columns" defined so using the defaults!
            return defaultColumns;
        }
        else
        {
            StringTokenizer tokenizer = new StringTokenizer(columns, ",;");
            List list = new LinkedList();

            while (tokenizer.hasMoreTokens())
            {
                String col = tokenizer.nextToken().toLowerCase().trim();

                if (defaultColumns.contains(col) && !list.contains(col))
                    list.add(col);
            }

            if (list.isEmpty())
                return defaultColumns;
            else
                return list;
        }
    }

    private Map prepareIconMap
            (Element
                    channel)
    {
        String imagesRoot = channel.getChild("link").getValue().toString() + "/images/icons/";
        Map result = new HashMap();

        JiraIconMappingManager iconMappingManager = (JiraIconMappingManager) ContainerManager.getComponent("jiraIconMappingManager");

        for (Iterator iterator = iconMappingManager.getIconMappings().entrySet().iterator(); iterator.hasNext();)
        {
            Map.Entry entry = (Map.Entry) iterator.next();
            String icon = (String) entry.getValue();
            if (icon.startsWith("http://") || icon.startsWith("https://"))
                result.put(entry.getKey(), icon);
            else
                result.put(GeneralUtil.escapeXml((String) entry.getKey()), imagesRoot + icon);
        }

        return result;
    }

    protected Element fetchChannel
            (String
                    url, boolean flushCache) throws IOException
    {
        Cache cache = cacheManager.getCache(JiraIssuesMacro.class.getName());

        Element channel;
        if (flushCache)
        {
            try
            {
                cache.remove(url);
            }
            catch (Exception e)
            {
                log.error("There was an error removing the JIRA Issues url (" + url + ") from the cache:", e);
            }
        }

        Object element = cache.get(url);

        if (element != null)
        {
            byte[] webContent = (byte[]) element;
            channel = getChannelElement(webContent, url);
            log.debug("XML Document feed " + url + " fetched from local cache!");
        }
        else
        {
            log.debug("Fetching XML feed " + url + " ...");
            HttpMethod method = null;
            InputStream in = null;
            try
            {
                method = retrieveRemoteUrl(url);
                ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
                in = method.getResponseBodyAsStream();

                int read;
                byte[] bytes = new byte[1024];
                while ((read = in.read(bytes)) != -1)
                    bytesOut.write(bytes, 0, read);

                byte[] webContent = bytesOut.toByteArray();
                channel = getChannelElement(webContent, url);

                cache.put(url, webContent);
            }
            finally
            {
                IOUtils.close(in);
                if (method != null)
                {
                    try
                    {
                        method.releaseConnection();
                    }
                    catch (Throwable t)
                    {
                        log.error("Error calling HttpMethod.releaseConnection", t);
                    }
                }
            }
        }
        return channel;
    }

    private Element getChannelElement(byte[] webContent, String url) throws IOException
    {
        ByteArrayInputStream bufferedIn = null;
        try
        {
            bufferedIn = new ByteArrayInputStream(webContent);
            SAXBuilder saxBuilder = new SAXBuilder("org.apache.xerces.parsers.SAXParser");
            Document jdomDocument = saxBuilder.build(bufferedIn);
            return (Element) XPath.selectSingleNode(jdomDocument, "/rss//channel");
        }
        catch (JDOMException e)
        {
            String filename = "rssoutput" + url.hashCode() + ".txt";
            // store retrieved output in a text file that can be downloaded
            File rssParseError = new File(bootstrapManager.getFilePathProperty(BootstrapManager.TEMP_DIR_PROP), filename);

            FileUtils.copyFile(new ByteArrayInputStream(webContent), rssParseError);


            String path = "/download/temp/" + filename;
            User user = getRemoteUser();

            if (user != null)
            {
                gateKeeper.addKey(path, user);
            }
            else
            {
                log.error("Could not reference remoteUser when trying to store results of RSS failure in temp dir.");
            }


            log.error("Error while trying to assemble the RSS result!", e);
            throw new IOException(e.getMessage() + " <a href='" + bootstrapManager.getWebAppContextPath() + "/download/temp/" + filename + "'>" + filename + "</a>");
        }
        finally
        {
            IOUtils.close(bufferedIn);
        }
    }


    /**
     * creates a URL including a refresh parameter to flush the cache of a macro (eg. JiraIssues Macro)
     *
     * @return String current url including the refresh parameter
     */
    protected String getRefreshUrl()
    {
        StringBuffer refreshUrl;
        HttpServletRequest request = ServletActionContext.getRequest();
        if (request != null)
        {
            refreshUrl = new StringBuffer(request.getRequestURI());
            String query = request.getQueryString();
            if (TextUtils.stringSet(query))
            {
                refreshUrl.append("?").append(query);

                if (request.getParameter(MACRO_REFRESH) == null)
                    refreshUrl.append("&").append(MACRO_REFRESH).append("=true");
            }
            else
                refreshUrl.append("?").append(MACRO_REFRESH).append("=true");

            return refreshUrl.toString();
        }
        return null;
    }

    /**
     * Checks the current url for a macro refresh parameter
     *
     * @return boolean whether or not the macro cache should be flushed
     */
    protected boolean flushCacheParameterSet()
    {
        HttpServletRequest request = ServletActionContext.getRequest();
        if (request != null)
        {
            String reloadParameter = request.getParameter(MACRO_REFRESH);
            if (reloadParameter != null)
            {
                return true;
            }
        }
        return false;
    }


    public void setBootstrapManager
            (BootstrapManager
                    bootstrapManager)
    {
        this.bootstrapManager = bootstrapManager;
    }

    public void setCacheManager
            (CacheManager
                    cacheManager)
    {
        this.cacheManager = cacheManager;
    }

    public void setGateKeeper
            (GateKeeper
                    gateKeeper)
    {
        this.gateKeeper = gateKeeper;
    }
}



