/*
 * Copyright 2014 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed 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.dashbuilder.dataset.json;

import org.dashbuilder.dataprovider.DataSetProviderRegistry;
import org.dashbuilder.dataprovider.DataSetProviderType;
import org.dashbuilder.dataset.ColumnType;
import org.dashbuilder.dataset.def.*;
import org.dashbuilder.dataset.filter.DataSetFilter;
import org.dashbuilder.json.Json;
import org.dashbuilder.json.JsonArray;
import org.dashbuilder.json.JsonException;
import org.dashbuilder.json.JsonObject;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * DataSetDef from/to JSON utilities
 */
public class DataSetDefJSONMarshaller {

    public static final String UUID = "uuid";
    public static final String NAME = "name";
    public static final String PROVIDER = "provider";
    public static final String ISPUBLIC = "isPublic";
    public static final String PUSH_ENABLED = "pushEnabled";
    public static final String PUSH_MAXSIZE = "pushMaxSize";
    public static final String COLUMNS = "columns";
    public static final String COLUMN_ID = "id";
    public static final String COLUMN_TYPE = "type";
    public static final String COLUMN_PATTERN = "pattern";
    public static final String FILTERS = "filters";
    public static final String ALL_COLUMNS = "allColumns";
    public static final String CACHE_ENABLED = "cacheEnabled";
    public static final String CACHE_MAXROWS = "cacheMaxRows";
    public static final String REFRESH_TIME = "refreshTime";
    public static final String REFRESH_ALWAYS = "refreshAlways";

    public static final List<String> ROOT_KEYS = Arrays.asList(
            UUID,
            NAME,
            PROVIDER,
            ISPUBLIC,
            PUSH_ENABLED,
            PUSH_MAXSIZE,
            COLUMNS,
            FILTERS,
            ALL_COLUMNS,
            CACHE_ENABLED,
            CACHE_MAXROWS,
            REFRESH_TIME,
            REFRESH_ALWAYS
    );

    protected DataSetProviderRegistry dataSetProviderRegistry;
    protected DataSetLookupJSONMarshaller dataSetLookupJSONMarshaller;

    public DataSetDefJSONMarshaller(DataSetProviderRegistry dataSetProviderRegistry) {
        this(dataSetProviderRegistry, DataSetLookupJSONMarshaller.get());
    }

    public DataSetDefJSONMarshaller(DataSetProviderRegistry dataSetProviderRegistry, DataSetLookupJSONMarshaller dataSetLookupJSONMarshaller) {
        this.dataSetProviderRegistry = dataSetProviderRegistry;
        this.dataSetLookupJSONMarshaller = dataSetLookupJSONMarshaller;
    }

    public DataSetDef fromJson(String jsonString) throws Exception {
        JsonObject json = Json.parse(jsonString);
        DataSetProviderType type = readProviderType(json);
        DataSetDef dataSetDef = type.createDataSetDef();
        dataSetDef.setProvider(type);

        readGeneralSettings(dataSetDef, json);

        DataSetDefJSONMarshallerExt marshaller = type.getJsonMarshaller();
        if (marshaller != null) {
            marshaller.fromJson(dataSetDef, json);
        }
        else {
            for (String key : json.keys()) {
                if (!ROOT_KEYS.contains(key)) {
                    String value = json.getString(key);
                    dataSetDef.setProperty(key, value);
                }
            }
        }
        return dataSetDef;
    }

    public DataSetProviderType readProviderType(JsonObject json) throws Exception {
        String provider = json.getString(PROVIDER);
        if (isBlank(provider)) {
            throw new IllegalArgumentException("Missing 'provider' property");
        }
        DataSetProviderType type = dataSetProviderRegistry.getProviderTypeByName(provider);
        if (type == null) {
            throw new IllegalArgumentException("Provider not supported: " + provider);
        }
        return type;
    }

    public DataSetDef readGeneralSettings(DataSetDef def, JsonObject json) throws Exception {
        String uuid = json.getString(UUID);
        String name = json.getString(NAME);
        String isPublic = json.getString(ISPUBLIC);
        String pushEnabled = json.getString(PUSH_ENABLED);
        String pushMaxSize = json.getString(PUSH_MAXSIZE);
        String cacheEnabled = json.getString(CACHE_ENABLED);
        String cacheMaxRows = json.getString(CACHE_MAXROWS);
        String refreshTime  = json.getString(REFRESH_TIME);
        String refreshAlways = json.getString(REFRESH_ALWAYS);
        String allColumns = json.getString(ALL_COLUMNS);

        if (!isBlank(uuid)) {
            def.setUUID(uuid);
        }
        if (!isBlank(name)) {
            def.setName(name);
        }
        if (!isBlank(isPublic)) {
            def.setPublic(Boolean.parseBoolean(isPublic));
        }
        if (!isBlank(pushEnabled)) {
            def.setPushEnabled(Boolean.parseBoolean(pushEnabled));
        }
        if (!isBlank(pushMaxSize)) {
            def.setPushMaxSize(Integer.parseInt(pushMaxSize));
        }
        if (!isBlank(cacheEnabled)) {
            def.setCacheEnabled(Boolean.parseBoolean(cacheEnabled));
        }
        if (!isBlank(cacheMaxRows)) {
            def.setCacheMaxRows(Integer.parseInt(cacheMaxRows));
        }
        if (!isBlank(refreshTime)) {
            def.setRefreshTime(refreshTime);
        }
        if (!isBlank(refreshAlways)) {
            def.setRefreshAlways(Boolean.parseBoolean(refreshAlways));
        }
        if (!isBlank(allColumns)) {
            def.setAllColumnsEnabled(Boolean.parseBoolean(allColumns));
        }

        if (json.has(COLUMNS)) {
            JsonArray array = json.getArray(COLUMNS);
            for (int i=0; i<array.length(); i++) {
                JsonObject column = array.getObject(i);
                String columnId = column.getString(COLUMN_ID);
                String columnType = column.getString(COLUMN_TYPE);
                String columnPattern = column.getString(COLUMN_PATTERN);

                if (isBlank(columnId)) {
                    throw new IllegalArgumentException("Column id. attribute is mandatory.");
                }
                if (isBlank(columnType)) {
                    throw new IllegalArgumentException("Missing column 'type' attribute: " + columnId);
                }

                ColumnType type = ColumnType.TEXT;
                if (columnType.equalsIgnoreCase("label")) {
                    type = ColumnType.LABEL;
                }
                else if (columnType.equalsIgnoreCase("date")) {
                    type = ColumnType.DATE;
                }
                else if (columnType.equalsIgnoreCase("number")) {
                    type = ColumnType.NUMBER;
                }

                def.addColumn(columnId, type);

                if (!isBlank(columnPattern)) {
                    def.setPattern(columnId, columnPattern);
                }
            }
        }
        if (json.has(FILTERS)) {
            JsonArray array = json.getArray(FILTERS);
            DataSetFilter dataSetFilter = dataSetLookupJSONMarshaller.parseFilterOperation(array);
            def.setDataSetFilter(dataSetFilter);
        }
        return def;
    }

    public String toJsonString(final DataSetDef dataSetDef) throws JsonException {
        return toJsonObject( dataSetDef ).toString();
    }

    public JsonObject toJsonObject(final DataSetDef dataSetDef) throws JsonException {
        JsonObject json = Json.createObject();

        // UUID.
        json.put(UUID, dataSetDef.getUUID());

        // Name.
        json.put(NAME, dataSetDef.getName());

        // Provider.
        json.put(PROVIDER, dataSetDef.getProvider().getName());

        // Public.
        json.put(ISPUBLIC, dataSetDef.isPublic());
        
        // Backend cache.
        json.put(CACHE_ENABLED, dataSetDef.isCacheEnabled());
        json.put(CACHE_MAXROWS, dataSetDef.getCacheMaxRows());

        // Client cache.
        json.put(PUSH_ENABLED, dataSetDef.isPushEnabled());
        json.put(PUSH_MAXSIZE, dataSetDef.getPushMaxSize());

        // Refresh.
        json.put(REFRESH_ALWAYS, dataSetDef.isRefreshAlways());
        json.put(REFRESH_TIME, dataSetDef.getRefreshTime());

        // Specific provider.
        DataSetProviderType type = dataSetDef.getProvider();
        DataSetDefJSONMarshallerExt marshaller = type.getJsonMarshaller();
        if (marshaller != null) {
            marshaller.toJson(dataSetDef, json);
        }
        
        // Data columns.
        final Collection<DataColumnDef> columns = dataSetDef.getColumns();
        if (columns != null)
        {
            final JsonArray columnsArray = toJsonObject(columns, dataSetDef);
            if (columnsArray != null)
            {
                json.put(COLUMNS, columnsArray);
            }
        }

        // Initial filter
        final DataSetFilter filter = dataSetDef.getDataSetFilter();
        if (filter != null) {
            try {
                final JsonArray filters = dataSetLookupJSONMarshaller.formatColumnFilters(filter.getColumnFilterList());
                if (filters != null) {
                    json.put(FILTERS, filters);
                }
            } catch (Exception e) {
                throw new JsonException(e);
            }
        }

        // Extra properties (only when no marshaller is provided)
        if (marshaller == null) {
            for (String key : dataSetDef.getPropertyNames()) {
                if (!ROOT_KEYS.contains(key)) {
                    String value = dataSetDef.getProperty(key);
                    json.put(key, value);
                }
            }
        }
        return json;
    }

    protected JsonArray toJsonObject(final Collection<DataColumnDef> columnList,
                                   final DataSetDef dataSetDef) throws JsonException {
        JsonArray result = null;
        if (columnList != null && !columnList.isEmpty()) {
            result = Json.createArray();
            int idx = 0;
            for (final DataColumnDef column : columnList) {
                final String id = column.getId();
                final ColumnType type = column.getColumnType();
                final JsonObject columnObject = Json.createObject();
                columnObject.put(COLUMN_ID, id);
                columnObject.put(COLUMN_TYPE, type.name().toLowerCase());
                String pattern = dataSetDef.getPattern(id);
                if (pattern != null && pattern.trim().length() > 0) {
                    columnObject.put(COLUMN_PATTERN, pattern);
                }
                result.set(idx++, columnObject);
            }
        }
        
        return result;
    }

    public static boolean isBlank(String str) {
        int strLen;
        if (str == null || (strLen = str.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isSpace(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }
}
