package fi.nls.oskari.control.statistics;

import fi.nls.oskari.annotation.OskariActionRoute;
import fi.nls.oskari.cache.JedisManager;
import fi.nls.oskari.control.ActionException;
import fi.nls.oskari.control.ActionHandler;
import fi.nls.oskari.control.ActionParameters;
import fi.nls.oskari.control.ActionParamsException;
import fi.nls.oskari.control.statistics.data.*;
import fi.nls.oskari.control.statistics.plugins.*;
import fi.nls.oskari.domain.User;
import fi.nls.oskari.util.ResponseHelper;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

/**
 * This interface gives the data for one indicator to the frontend for showing it on the map and on the table.
 * 
 * - action_route=GetIndicatorData&plugin_id=plugin_id&indicator_id=indicator_id&layer_id=layer_id&selectors=URL_ENCODED_JSON
 * 
 * For example SotkaNET requires selectors for year and gender. This means that selectors parameter content
 * could be for example: selectors=%7B%22gender%22%3A%20%22male%22%2C%20%22year%22%3A%20%222005%22%7D
 * 
 * Response is in JSON, and contains the indicator data.
 */
@OskariActionRoute("GetIndicatorData")
public class GetIndicatorDataHandler extends ActionHandler {

    private final static String PARAM_PLUGIN_ID = "datasource"; // previously plugin_id
    private final static String PARAM_INDICATOR_ID = "indicator"; // previously indicator_id
    private final static String PARAM_LAYER_ID = "regionset"; // previously layer_id
    private final static String PARAM_SELECTORS = "selectors";

    /**
     * For now, this uses pretty much static global store for the plugins.
     * In the future it might make sense to inject the pluginManager references to different controllers using DI.
     */
    private static final StatisticalDatasourcePluginManager PLUGIN_MANAGER = StatisticalDatasourcePluginManager.getInstance();

    @Override
    public void handleAction(ActionParameters ap) throws ActionException {
        final long pluginId = ap.getRequiredParamLong(PARAM_PLUGIN_ID);
        final String indicatorId = ap.getRequiredParam(PARAM_INDICATOR_ID);
        final long layerId = ap.getRequiredParamLong(PARAM_LAYER_ID);
        final String selectors = ap.getRequiredParam(PARAM_SELECTORS);
        JSONObject selectorsJSON;
        try {
            selectorsJSON = new JSONObject(selectors);
        } catch (JSONException e) {
            throw new ActionParamsException("Invalid parameter value "
                    + PARAM_SELECTORS + " expected JSON object");
        }
        JSONObject response = getIndicatorDataJSON(ap.getUser(), pluginId, indicatorId, layerId, selectorsJSON);
        ResponseHelper.writeResponse(ap, response);
    }

    public JSONObject getIndicatorDataJSON(User user, long pluginId, String indicatorId,
            long layerId, JSONObject selectorJSON) throws ActionException {
        final StatisticalDatasourcePlugin plugin = PLUGIN_MANAGER.getPlugin(pluginId);

        final String cacheKey;
        if (plugin.canCache()) {
            cacheKey = GetIndicatorDataHelper.getCacheKey(pluginId, indicatorId, layerId, selectorJSON);
            String cachedData = JedisManager.get(cacheKey);
            if (cachedData != null && !cachedData.isEmpty()) {
                try {
                    return new JSONObject(cachedData);
                } catch (JSONException e) {
                    // Failed serializing. Skipping the cache.
                }
            }
        } else {
            cacheKey = null;
        }

        JSONObject response;
        try {

            // TODO: Might be faster to store the indicator id to indicator map in a proper map.
            //       Who should do this, though? We don't want to put this functionality into the plugins.
            //       It should be in a common wrapper for the plugins.

            StatisticalIndicator indicator = plugin.getIndicator(user, indicatorId);
            if(indicator == null) {
                throw new ActionParamsException("No such indicator");
            }

            StatisticalIndicatorLayer layer = indicator.getLayer(layerId);
            if(layer == null) {
                throw new ActionParamsException("No such regionset");
            }

            // Note: Layer version is handled already in the indicator metadata.
            // We found the correct indicator and the layer.

            StatisticalIndicatorDataModel selectors = new StatisticalIndicatorDataModel();
            @SuppressWarnings("unchecked")
            Iterator<String> keys = selectorJSON.keys();
            while (keys.hasNext()) {
                String key = keys.next();
                String value = selectorJSON.getString(key);
                StatisticalIndicatorDataDimension selector = new StatisticalIndicatorDataDimension(key, value);
                selectors.addDimension(selector);
            }
            Map<String, IndicatorValue> values = plugin.getIndicatorValues(indicator, selectors, layer);
            response = toJSON(values);
        } catch (Exception e) {
            if(e instanceof ActionException) {
                throw (ActionException)e;
            }
            throw new ActionException("Something went wrong in serializing indicator data.", e);
        }
        // Note that there is an another layer of caches in the plugins doing the web queries.
        // Two layers are necessary, because deserialization and conversion to the internal data model
        // is a pretty heavy operation.
        if (plugin.canCache() && response != null) {
            JedisManager.setex(cacheKey, JedisManager.EXPIRY_TIME_DAY, response.toString());
        }
        return response;
    }

    private JSONObject toJSON(Map<String, IndicatorValue> values) throws JSONException {
        JSONObject json = new JSONObject();
        for (Entry<String, IndicatorValue> entry : values.entrySet()) {
            entry.getValue().putToJSONObject(json, entry.getKey());
        }
        return json;
    }
}

@@ -446,24 +446,62 @@
 omain.User;%0A
+import fi.nls.oskari.util.JSONHelper;%0A
 import fi.nl
@@ -667,16 +667,43 @@
 p.Entry;
+%0Aimport java.util.Optional;
 %0A%0A/**%0A *
@@ -3099,63 +3099,8 @@
 ing 
-cacheKey;%0A        if (plugin.canCache()) %7B%0A            
 cach
@@ -3200,175 +3200,44 @@
     
-    String cachedData = JedisManager.get(cacheKey);%0A            if (cachedData != null && !cachedData.isEmpty()) %7B%0A                try %7B%0A                    return new
+if (plugin.canCache()) %7B%0A           
  JSO
@@ -3247,19 +3247,39 @@
 ject
-(
+ 
 cached
-Data
+ = getFromCache(cacheKey
 );%0A 
@@ -3293,36 +3293,26 @@
     
-    %7D 
+if (
 ca
-t
 ch
- (JSONException e
+ed != null
 ) %7B%0A
@@ -3331,57 +3331,23 @@
     
-    // Failed serializing. Skipping the
+return
  cache
-.%0A  
+d;%0A
     
@@ -3358,71 +3358,9 @@
     
-  %7D%0A            %7D%0A        %7D else %7B%0A            cacheKey = null;
+%7D
 %0A   
@@ -5542,24 +5542,198 @@
 nse;%0A    %7D%0A%0A
+    private JSONObject getFromCache(String cacheKey) %7B%0A        String cachedData = JedisManager.get(cacheKey);%0A        return JSONHelper.createJSONObject(cachedData);%0A    %7D%0A%0A
     private 

Move getting from cache to a separate function