/*
 * Copyright (c) 2008-2013 Haulmont. All rights reserved.
 * Use is subject to license terms, see http://www.cuba-platform.com/license for details.
 */
package com.haulmont.cuba.core.sys.querymacro;

import com.google.common.collect.ImmutableMap;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.TimeSource;
import com.google.common.base.Strings;
import com.haulmont.cuba.core.global.Scripting;
import com.haulmont.cuba.core.sys.QueryMacroHandler;
import groovy.lang.Binding;
import org.apache.commons.lang.time.DateUtils;
import org.springframework.context.annotation.Scope;

import org.springframework.stereotype.Component;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author krivopustov
 * @version $Id$
 */
@Component("cuba_TimeBetweenQueryMacroHandler")
@Scope("prototype")
public class TimeBetweenQueryMacroHandler implements QueryMacroHandler {

    protected static final Pattern MACRO_PATTERN = Pattern.compile("@between\\s*\\(([^\\)]+)\\)");
    protected static final Pattern PARAM_PATTERN = Pattern.compile("(now)\\s*([\\d\\s+-]*)");
    protected static final Pattern QUERY_PARAM_PATTERN = Pattern.compile(":(\\w+)");

    protected static final Map<String, Object> units =
            new ImmutableMap.Builder<String, Object>()
                    .put("year", Calendar.YEAR)
                    .put("month", Calendar.MONTH)
                    .put("day", Calendar.DAY_OF_MONTH)
                    .put("hour", Calendar.HOUR_OF_DAY)
                    .put("minute", Calendar.MINUTE)
                    .put("second", Calendar.SECOND)
                    .build();

    protected int count;
    protected Map<String, Object> params = new HashMap<>();

    @Override
    public String expandMacro(String queryString) {
        count = 0;
        Matcher matcher = MACRO_PATTERN.matcher(queryString);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, doExpand(matcher.group(1)));
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    @Override
    public void setQueryParams(Map<String, Object> namedParameters) {
    }

    @Override
    public Map<String, Object> getParams() {
        return params;
    }

    @Override
    public String replaceQueryParams(String queryString, Map<String, Object> params) {
        Matcher matcher = MACRO_PATTERN.matcher(queryString);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String macros = matcher.group(0);
            macros = replaceParamsInMacros(macros, params);
            matcher.appendReplacement(sb, macros);
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    protected String replaceParamsInMacros(String macros, Map<String, Object> params) {
        Matcher matcher = QUERY_PARAM_PATTERN.matcher(macros);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String paramName = matcher.group(1);
            if (params.containsKey(paramName)) {
                matcher.appendReplacement(sb, params.get(paramName).toString());
                params.remove(paramName);
            }
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    protected String doExpand(String macro) {
        count++;
        String[] args = macro.split(",");
        if (args.length != 4)
            throw new RuntimeException("Invalid macro: " + macro);

        String field = args[0];
        String param1 = getParam(args, 1);
        String param2 = getParam(args, 2);

        return String.format("(%s >= :%s and %s < :%s)", field, param1, field, param2);
    }

    protected String getParam(String[] args, int idx) {
        String arg = args[idx].trim();
        String unit = args[3].trim();

        Matcher matcher = PARAM_PATTERN.matcher(arg);
        if (!matcher.find())
            throw new RuntimeException("Invalid macro argument: " + arg);

        int num = 0;
        try {
            String expr = matcher.group(2);
            if (!Strings.isNullOrEmpty(expr)) {
                Scripting scripting = AppBeans.get(Scripting.class);
                num = scripting.evaluateGroovy(expr, new Binding());
            }
        } catch (NumberFormatException e) {
            throw new RuntimeException("Invalid macro argument: " + arg, e);
        }

        Date date = computeDate(num, unit);

        String paramName = args[0].trim().replace(".", "_") + "_" + count + "_" + idx;
        params.put(paramName, date);

        return paramName;
    }

    protected Date computeDate(int num, String unit) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(AppBeans.get(TimeSource.class).currentTimestamp());
        int calField1 = getCalendarField(unit);
        if (num != 0) {
            cal.add(calField1, num);
        }
        int calField = calField1;
        Date date = DateUtils.truncate(cal.getTime(), calField);
        return date;
    }

    protected int getCalendarField(String unit) {
        Integer calField = (Integer) units.get(unit.toLowerCase());
        if (calField == null)
            throw new RuntimeException("Invalid macro argument: " + unit);
        return calField;
    }
}
@@ -1304,79 +1304,49 @@
 ts =
-%0D%0A            new ImmutableMap.Builder%3CString, Object
+ new HashMap%3C
 %3E()
+;
 %0D%0A    
- 
+static %7B%0D%0A
         
     
@@ -1337,31 +1337,29 @@
  %7B%0D%0A        
-       
+units
 .put(%22year%22,
@@ -1373,16 +1373,17 @@
 ar.YEAR)
+;
 %0D%0A      
@@ -1380,36 +1380,29 @@
 );%0D%0A        
- 
-           
+units
 .put(%22month%22
@@ -1414,24 +1414,25 @@
 endar.MONTH)
+;
 %0D%0A          
@@ -1425,36 +1425,29 @@
 );%0D%0A        
-            
+units
 .put(%22day%22, 
@@ -1468,16 +1468,17 @@
 F_MONTH)
+;
 %0D%0A      
@@ -1475,36 +1475,29 @@
 );%0D%0A        
-            
+units
 .put(%22hour%22,
@@ -1518,16 +1518,17 @@
 _OF_DAY)
+;
 %0D%0A      
@@ -1525,36 +1525,29 @@
 );%0D%0A        
-            
+units
 .put(%22minute
@@ -1565,16 +1565,17 @@
 .MINUTE)
+;
 %0D%0A      
@@ -1576,28 +1576,21 @@
         
-            
+units
 .put(%22se
@@ -1616,39 +1616,16 @@
 OND)
+;
 %0D%0A    
-   
-             .build();
+%7D
 %0D%0A%0D%0A

Fix findbugs warnings #PL-6147