/*
 * 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.
 */


//$Id$

package org.apache.oodt.cas.metadata.util;

import org.apache.commons.lang.StringUtils;
import org.apache.oodt.cas.metadata.Metadata;
import org.apache.oodt.cas.metadata.exceptions.CasMetadataException;
import org.apache.oodt.commons.date.DateUtils;
import org.apache.oodt.commons.exceptions.CommonsException;
import org.apache.oodt.commons.exec.EnvUtilities;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author mattmann
 * @author bfoster
 * @version $Revision$
 * 
 * <p>
 * A Utility class for replacing environment variables and maniuplating file
 * path strings.
 * </p>.
 */
public final class PathUtils {

    public static String DELIMITER = ",";

    public static String replaceEnvVariables(String origPath) {
        return replaceEnvVariables(origPath, null);
    }

    public static String replaceEnvVariables(String origPath, Metadata metadata) {
        return replaceEnvVariables(origPath, metadata, false);
    }

    public static String replaceEnvVariables(String origPath,
            Metadata metadata, boolean expand) {
        StringBuilder finalPath = new StringBuilder();

        for (int i = 0; i < origPath.length(); i++) {
            if (origPath.charAt(i) == '[') {
                VarData data = readEnvVarName(origPath, i);
                String var;
                if (metadata != null
                        && metadata.getMetadata(data.getFieldName()) != null) {
                    List valList = metadata.getAllMetadata(data.getFieldName());
                    var = (String) valList.get(0);
                    if (expand) {
                        for (int j = 1; j < valList.size(); j++) {
                            var += DELIMITER + (String) valList.get(j);
                        }
                    }
                } else {
                    var = EnvUtilities.getEnv(data.getFieldName());
                }
                finalPath.append(var);
                i = data.getEndIdx();
            } else {
                finalPath.append(origPath.charAt(i));
            }
        }

        return finalPath.toString();
    }

    public static String doDynamicReplacement(String string)
        throws ParseException, CommonsException, CasMetadataException {
        return doDynamicReplacement(string, null);
    }

    public static String doDynamicReplacement(String string, Metadata metadata)
        throws ParseException, CommonsException, CasMetadataException {
        return PathUtils.replaceEnvVariables(doDynamicDateReplacement(
        		   doDynamicDateRollReplacement(
        			   doDynamicDateFormatReplacement(
                           doDynamicUtcToTaiDateReplacement(
                               doDynamicDateToSecsReplacement(
                                   doDynamicDateToMillisReplacement(
                                       string, metadata),
                                   metadata),
                               metadata),
                           metadata), 
                       metadata),
                   metadata),
               metadata, true);
    }

    public static String doDynamicDateReplacement(String string,
            Metadata metadata) throws CasMetadataException, CommonsException {
        Pattern datePattern = Pattern
                .compile("\\[\\s*DATE\\s*(?:[+-]{1}[^\\.]{1,}?){0,1}\\.\\s*(?:(?:DAY)|(?:MONTH)|(?:YEAR)|(?:UTC)|(?:TAI)){1}\\s*\\]");
        Matcher dateMatcher = datePattern.matcher(string);
        while (dateMatcher.find()) {
            String dateString = string.substring(dateMatcher.start(),
                    dateMatcher.end());
            GregorianCalendar gc = new GregorianCalendar();

            // roll the date if roll days was specfied
            int plusMinusIndex;
            if ((plusMinusIndex = dateString.indexOf('-')) != -1
                    || (plusMinusIndex = dateString.indexOf('+')) != -1) {
                int dotIndex;
                if ((dotIndex = dateString.indexOf('.', plusMinusIndex)) != -1) {
                    int rollDays = Integer.parseInt(PathUtils
                            .replaceEnvVariables(
                                    dateString.substring(plusMinusIndex,
                                            dotIndex), metadata).replaceAll(
                                    "[\\+\\s]", ""));
                    gc.add(GregorianCalendar.DAY_OF_YEAR, rollDays);
                } else {
                    throw new CasMetadataException(
                        "Malformed dynamic date replacement specified (no dot separator) - '"
                        + dateString + "'");
                }
            }

            // determine type and make the appropriate replacement
            String[] splitDate = dateString.split("\\.");
            if (splitDate.length < 2) {
                throw new CasMetadataException("No date type specified - '" + dateString
                                               + "'");
            }
            String dateType = splitDate[1].replaceAll("[\\[\\]\\s]", "");
            String replacement;
            if (dateType.equals("DAY")) {
                replacement = StringUtils.leftPad(gc
                        .get(GregorianCalendar.DAY_OF_MONTH)
                        + "", 2, "0");
            } else if (dateType.equals("MONTH")) {
                replacement = StringUtils.leftPad((gc
                        .get(GregorianCalendar.MONTH) + 1)
                        + "", 2, "0");
            } else if (dateType.equals("YEAR")) {
                replacement = gc.get(GregorianCalendar.YEAR) + "";
            } else if (dateType.equals("UTC")) {
                replacement = DateUtils.toString(DateUtils.toUtc(gc));
            } else if (dateType.equals("TAI")) {
                replacement = DateUtils.toString(DateUtils.toTai(gc));
            } else {
                throw new CasMetadataException("Invalid date type specified '"
                        + dateString + "'");
            }

            string = StringUtils.replace(string, dateString, replacement);
            dateMatcher = datePattern.matcher(string);
        }
        return string;
    }

    /**
     * usage format: [DATE_ADD(<date>,<date-format>,<add-amount>,<hr | min | sec | day | mo | yr>)]
     * example: [DATE_ADD(2009-12-31, yyyy-MM-dd, 1, day)] . . . output will be: 2010-01-01
     * - dynamic replacement is allowed for the <date> as well, for example: 
     *  [DATE_ADD([DATE.UTC], yyyy-MM-dd'T'HH:mm:ss.SSS'Z', 1, day)] will add one day to the 
     *  current UTC time
     */
    public static String doDynamicDateRollReplacement(String string,
            Metadata metadata) throws ParseException, CasMetadataException, CommonsException {
        Pattern dateFormatPattern = Pattern
                .compile("\\[\\s*DATE_ADD\\s*\\(.{1,}?,.{1,}?,.{1,}?,.{1,}?\\)\\s*\\]");
        Matcher dateFormatMatcher = dateFormatPattern.matcher(string);
        while (dateFormatMatcher.find()) {
            String dateFormatString = string.substring(dateFormatMatcher
                    .start(), dateFormatMatcher.end());

            // get arguments
            Matcher argMatcher = Pattern.compile("\\(.*\\)").matcher(
                    dateFormatString);
            argMatcher.find();
            String argsString = dateFormatString.substring(argMatcher.start() + 1,
                    argMatcher.end() - 1); 
            argsString = doDynamicReplacement(argsString, metadata);
            String[] args = argsString.split(",");
            String dateString = args[0].trim();
            dateString = doDynamicReplacement(dateString, metadata);
            String dateFormat = args[1].trim();
            int addAmount = Integer.parseInt(args[2].trim());
            String addUnits = args[3].trim().toLowerCase();

            // reformat date
            Date date = new SimpleDateFormat(dateFormat).parse(dateString);
            Calendar calendar = (Calendar) Calendar.getInstance().clone();
            calendar.setTime(date);
            if (addUnits.equals("hr") || addUnits.equals("hour")) {
                calendar.add(Calendar.HOUR_OF_DAY, addAmount);
            } else if (addUnits.equals("min") || addUnits.equals("minute")) {
                calendar.add(Calendar.MINUTE, addAmount);
            } else if (addUnits.equals("sec") || addUnits.equals("second")) {
                calendar.add(Calendar.SECOND, addAmount);
            } else if (addUnits.equals("day")) {
                calendar.add(Calendar.DAY_OF_YEAR, addAmount);
            } else if (addUnits.equals("mo") || addUnits.equals("month")) {
                calendar.add(Calendar.MONTH, addAmount);
            } else if (addUnits.equals("yr") || addUnits.equals("year")) {
                calendar.add(Calendar.YEAR, addAmount);
            }
            
            String newDateString = new SimpleDateFormat(dateFormat).format(calendar.getTime());
            
            // swap in date string
            string = StringUtils.replace(string, dateFormatString,
                    newDateString);
            dateFormatMatcher = dateFormatPattern.matcher(string);
        }

        return string;
    }
    
    public static String doDynamicDateFormatReplacement(String string,
            Metadata metadata) throws ParseException, CasMetadataException, CommonsException {
        Pattern dateFormatPattern = Pattern
                .compile("\\[\\s*FORMAT\\s*\\(.{1,}?,.{1,}?,.{1,}?\\)\\s*\\]");
        Matcher dateFormatMatcher = dateFormatPattern.matcher(string);
        while (dateFormatMatcher.find()) {
            String dateFormatString = string.substring(dateFormatMatcher
                    .start(), dateFormatMatcher.end());

            // get arguments
            Matcher argMatcher = Pattern.compile("\\(.*\\)").matcher(
                    dateFormatString);
            argMatcher.find();
            String argsString = dateFormatString.substring(argMatcher.start() + 1,
                    argMatcher.end() - 1); 
            argsString = doDynamicReplacement(argsString, metadata);
            String[] args = argsString.split(",");
            String curFormat = args[0].trim();
            String dateString = args[1].trim();
            String newFormat = args[2].trim();

            // reformat date
            Date date = new SimpleDateFormat(curFormat).parse(dateString);
            String newDateString = new SimpleDateFormat(newFormat).format(date);

            // swap in date string
            string = StringUtils.replace(string, dateFormatString,
                    newDateString);
            dateFormatMatcher = dateFormatPattern.matcher(string);
        }

        return string;
    }
    
    /**
     * Replaces String method of format [UTC_TO_TAI(<utc-string format: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'">)]
     * with TAI time with format: "yyyy-MM-dd'T'HH:mm:ss.SSS-0000<leapSecs>"
     */
    public static String doDynamicUtcToTaiDateReplacement(String string,
            Metadata metadata) throws ParseException, CommonsException, CasMetadataException {
        Pattern utcToTaiPattern = Pattern.compile("\\[\\s*UTC_TO_TAI\\s*\\(.{1,}?\\)\\s*\\]");
        Matcher matcher = utcToTaiPattern.matcher(string);
        while (matcher.find()) {
            String utcToTaiString = string.substring(matcher.start(), matcher.end());
            Matcher argMatcher = Pattern.compile("\\(.*\\)").matcher(utcToTaiString);
            argMatcher.find();
            String utcDateString = 
                utcToTaiString.substring(argMatcher.start() + 1, argMatcher.end() - 1).trim();
            utcDateString = doDynamicReplacement(utcDateString, metadata);
            string = StringUtils.replace(string, utcToTaiString, 
                    DateUtils.toString(DateUtils.toTai(DateUtils.toCalendar(utcDateString, 
                            DateUtils.FormatType.UTC_FORMAT))));
            matcher = utcToTaiPattern.matcher(string);
        }
        return string;
    }
    
    /**
     * Replaces String method of format [DATE_TO_SECS(<date-string>,<DateUtils.FormatType>,<epoch-date format: "yyyy-MM-dd">)]
     * with seconds between <epoch-date> and <date-string> 
     */
    public static String doDynamicDateToSecsReplacement(String string,
            Metadata metadata) throws CommonsException, ParseException, CasMetadataException {
        Pattern utcToTaiPattern = Pattern.compile("\\[\\s*DATE_TO_SECS\\s*\\(.{1,}?\\,.{1,}?,.{1,}?\\)\\s*\\]");
        Matcher matcher = utcToTaiPattern.matcher(string);
        while (matcher.find()) {
            String dateToSecsString = string.substring(matcher.start(), matcher.end());
            Matcher argMatcher = Pattern.compile("\\(.*\\)").matcher(dateToSecsString);
            argMatcher.find();
            String argsString = dateToSecsString.substring(argMatcher.start() + 1,
                    argMatcher.end() - 1);
            argsString = doDynamicReplacement(argsString, metadata);
            String[] args = argsString.split(",");
            String dateString = args[0].trim();
            String dateType = args[1].trim();
            String epochString = args[2].trim();
            Calendar date = DateUtils.toCalendar(dateString, DateUtils.FormatType.valueOf(dateType));
            Calendar epoch = DateUtils.toLocalCustomFormatCalendar(epochString, "yyyy-MM-dd");
            String seconds = DateUtils.toString(DateUtils.getTimeInSecs(date, epoch));
            string = StringUtils.replace(string, dateToSecsString, seconds);
            matcher = utcToTaiPattern.matcher(string);
        }
        return string;
    }
    
    /**
     * Replaces String method of format [DATE_TO_MILLIS(<date-string>,<DateUtils.FormatType>,<epoch-date format: "yyyy-MM-dd">)]
     * with milliseconds between <epoch-date> and <date-string> 
     */
    public static String doDynamicDateToMillisReplacement(String string,
            Metadata metadata) throws ParseException, CommonsException, CasMetadataException {
        Pattern utcToTaiPattern = Pattern.compile("\\[\\s*DATE_TO_MILLIS\\s*\\(.{1,}?\\,.{1,}?,.{1,}?\\)\\s*\\]");
        Matcher matcher = utcToTaiPattern.matcher(string);
        while (matcher.find()) {
            String dateToMillisString = string.substring(matcher.start(), matcher.end());
            Matcher argMatcher = Pattern.compile("\\(.*\\)").matcher(dateToMillisString);
            argMatcher.find();
            String argsString = dateToMillisString.substring(argMatcher.start() + 1,
                    argMatcher.end() - 1);
            argsString = doDynamicReplacement(argsString, metadata);
            String[] args = argsString.split(",");
            String dateString = args[0].trim();
            String dateType = args[1].trim();
            String epochString = args[2].trim();
            Calendar date = DateUtils.toCalendar(dateString, DateUtils.FormatType.valueOf(dateType));
            Calendar epoch = DateUtils.toLocalCustomFormatCalendar(epochString, "yyyy-MM-dd");
            String milliseconds = DateUtils.getTimeInMillis(date, epoch) + "";
            string = StringUtils.replace(string, dateToMillisString, milliseconds);
            matcher = utcToTaiPattern.matcher(string);
        }
        return string;
    }

    private static VarData readEnvVarName(String origPathStr, int startIdx) {
        StringBuilder varName = new StringBuilder();
        int idx = startIdx + 1;

        do {
            varName.append(origPathStr.charAt(idx));
            idx++;
        } while (origPathStr.charAt(idx) != ']');

        VarData data = new PathUtils().new VarData();
        data.setFieldName(varName.toString());
        data.setEndIdx(idx);
        return data;

    }

    class VarData {

        private String fieldName = null;

        private int endIdx = -1;

        public VarData() {
        }

        /**
         * @return the endIdx
         */
        public int getEndIdx() {
            return endIdx;
        }

        /**
         * @param endIdx
         *            the endIdx to set
         */
        public void setEndIdx(int endIdx) {
            this.endIdx = endIdx;
        }

        /**
         * @return the fieldName
         */
        public String getFieldName() {
            return fieldName;
        }

        /**
         * @param fieldName
         *            the fieldName to set
         */
        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

    }

}