/*
 *  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 org.apache.synapse.mediators.transform.url;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.apache.synapse.MessageContext;

import java.net.URISyntaxException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Represents a URL rewrite action. The action could be rewriting the entire URL
 * or rewriting a fragment of the URL.
 */
public class RewriteAction {

    private static final Log log = LogFactory.getLog(RewriteAction.class);

    public static final int ACTION_SET      = 0;
    public static final int ACTION_APPEND   = 1;
    public static final int ACTION_PREPEND  = 2;
    public static final int ACTION_REPLACE  = 3;
    public static final int ACTION_REMOVE   = 4;
    public static final int ACTION_REMOVE_QUERY_PARAM = 5;

    private String value;
    private SynapseXPath xpath;
    private String regex;
    private int fragmentIndex = URIFragments.FULL_URI;
    private int actionType = ACTION_SET;
    private boolean resolve = false;
    private static final String QUERY_PARAM_DELIMETER = "&";

    private static String pathParamRegex = "[^{}]*\\{([^{}]*)}";
    private static Pattern pathParamPattern =  Pattern.compile(pathParamRegex);

    public void execute(URIFragments fragments,
                        MessageContext messageContext) throws URISyntaxException {

        String result;
        if (xpath != null) {
            result = xpath.stringValueOf(messageContext);
        } else {
            result = value;
        }

        if (fragmentIndex == URIFragments.FULL_URI) {
            URI uri;
            if (result != null) {
                uri = new URI(result);
                if (log.isTraceEnabled()) {
                    log.trace("Setting the URI to: " + result);
                }
            } else {
                uri = new URI("");
            }

            // Since the entire URL has been rewritten we need to reinit all the fragments
            fragments.setFragments(uri);

        } else if (fragmentIndex == URIFragments.PORT) {
            // When setting the port we must first convert the value into an integer
            if (result != null) {
                fragments.setPort(Integer.parseInt(result));
            } else {
                fragments.setPort(-1);
            }
        } else {
            String str;
            String currentValue = fragments.getStringFragment(fragmentIndex);
            if (currentValue == null) {
                currentValue = "";
            }

			switch (actionType) {
				case ACTION_PREPEND:
				    if (result != null) {
				        if (fragmentIndex == URIFragments.QUERY && !StringUtils.isEmpty(currentValue)) {
                            str = result.concat(QUERY_PARAM_DELIMETER + currentValue);
                        } else {
                            str = result.concat(currentValue);
                        }
                    } else {
				        str = "";
                    }
					break;
				case ACTION_APPEND:
					if (result != null) {
                        if (fragmentIndex == URIFragments.QUERY && !StringUtils.isEmpty(currentValue)) {
                            str = currentValue.concat(QUERY_PARAM_DELIMETER + result);
                        } else {
                            str = currentValue.concat(result);
                        }
					} else {
						str = "";
					}
					break;
				case ACTION_REPLACE:
					if (result != null) {
						str = currentValue.replaceAll(regex, result);
					} else {
						str = "";
					}
					break;
				case ACTION_REMOVE:
					str = null;
					break;
                case ACTION_REMOVE_QUERY_PARAM:
                    if (!StringUtils.isEmpty(currentValue)) {
                        String[] queryParams = currentValue.split(QUERY_PARAM_DELIMETER);
                        List<String> queryParamList = new ArrayList<>(Arrays.asList(queryParams));
                        Iterator iterator = queryParamList.iterator();

                        Pattern pattern = Pattern.compile(result + "=.*");
                        Matcher matcher;
                        while (iterator.hasNext()) {
                            String s = (String)iterator.next();
                            matcher = pattern.matcher(s);
                            if (matcher.find()) {
                                iterator.remove();
                                break;
                            }
                        }
                        str = String.join(QUERY_PARAM_DELIMETER, queryParamList);
                    } else {
                        str = "";
                    }
                    break;
				default:
					str = result;
			}

            if (resolve) {
                Matcher matcher = pathParamPattern.matcher(str);
                while (matcher.find()) {
                    String pathParam = matcher.group(1);
                    String paramValue = (String) messageContext.getProperty(pathParam);
                    if (!StringUtils.isEmpty(paramValue)) {
                        str = str.replace("{" + pathParam + "}", paramValue);
                    } else {
                        log.error("Path parameter '" + pathParam + "' was not found in the provided URL postfix");
                        str = "";
                    }
                }
            }
			
            fragments.setStringFragment(fragmentIndex, str);
        }
    }

    public int getFragmentIndex() {
        return fragmentIndex;
    }

    public void setFragmentIndex(int fragmentIndex) {
        this.fragmentIndex = fragmentIndex;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public SynapseXPath getXpath() {
        return xpath;
    }

    public void setXpath(SynapseXPath xpath) {
        this.xpath = xpath;
    }

    public String getRegex() {
        return regex;
    }

    public void setRegex(String regex) {
        this.regex = regex;
    }

    public int getActionType() {
        return actionType;
    }

    public void setActionType(int actionType) {
        this.actionType = actionType;
    }

    public boolean isResolve() {
        return resolve;
    }

    public void setResolve(boolean resolve) {
        this.resolve = resolve;
    }
}
