/*******************************************************************************
 * Copyright 2019 2Fi Business Solutions Ltd.
 *
 * This code is part of the Core project.
 *
 * This code is copyrighted. Under no circumstances should any party, people,
 * or organization should redistribute any portions of this code in any form,
 * either verbatim or through electronic media, to any third parties, unless
 * under explicit written permission by 2Fi Business Solutions Ltd.
 ******************************************************************************/
package com.ffii.core.setting.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import com.ffii.core.dao.JdbcDao;
import com.ffii.core.setting.Setting;
import com.ffii.core.utils.NumberUtils;
import com.ffii.core.utils.Params;

/**
 * System Settings Service
 *
 * @author Patrick
 */
@Service
public class SettingsService {

	private static final String SQL_LOAD_SETTINGS = "SELECT * FROM settings ORDER BY category, name";

	private static final String SQL_INSERT_OR_UPDATE_SETTING = "INSERT INTO settings (`name`, `value`) VALUES (:name, :value) ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `value` = VALUES(`value`)";

	/**
	 * Settings cache
	 */
	private static Map<String, Setting> SETTINGS = null;

	@Autowired
	private JdbcDao jdbcDao;

	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public List<Map<String, Object>> searchSettings(Map<String, Object> args) {

		StringBuilder sql = new StringBuilder("SELECT * FROM settings WHERE 1=1");

		if (args.containsKey("category")) sql.append(" AND category = :category");
		if (args.containsKey("type")) sql.append(" AND type = :type");
		if (args.containsKey("name")) sql.append(" AND name = :name");

		sql.append(" ORDER BY category, name");

		return jdbcDao.queryForList(sql.toString(), args);
	}

	/**
	 * Load and return a Map of all system settings from database
	 */
	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public Map<String, Setting> loadSettingsMap() {
		List<Setting> settings = jdbcDao.query(SQL_LOAD_SETTINGS, Setting.class, null, 0);
		Map<String, Setting> settingsMap = new HashMap<String, Setting>();
		for (Setting setting : settings) {
			settingsMap.put(setting.getName(), setting);
		}
		return settingsMap;
	}

	/**
	 * @param name
	 *            the name of the setting
	 * @param defaultValue
	 *            the default value to return if setting is not found
	 *
	 * @return the String value of the setting, or the default value if not found
	 */
	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public String getString(String name, String defaultValue) {
		if (SETTINGS == null) SETTINGS = loadSettingsMap();
		return SETTINGS.get(name) != null ? SETTINGS.get(name).getValue() : defaultValue;
	}

	/**
	 * @param name
	 *            the name of the setting
	 *
	 * @return the String value of the setting, or {@code null} if not found
	 */
	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public String getString(String name) {
		return getString(name, null);
	}

	/**
	 * @param name
	 *            the name of the setting
	 * @param defaultValue
	 *            the default value to return if setting is not found
	 *
	 * @return the int value of the setting, or the default value if not found
	 */
	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public int getInt(String name, int defaultValue) {
		return NumberUtils.toInt(getString(name), defaultValue);
	}

	/**
	 * @param name
	 *            the name of the setting
	 *
	 * @return the int value of the setting, or zero (0) if not found
	 */
	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public int getInt(String name) {
		return getInt(name, 0);
	}

	/**
	 * @param name
	 *            the name of the setting
	 * @param defaultValue
	 *            the default value to return if setting is not found
	 *
	 * @return the double value of the setting, or the default value if not found
	 */
	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public double getDouble(String name, double defaultValue) {
		return NumberUtils.toDouble(getString(name), defaultValue);
	}

	/**
	 * @param name
	 *            the name of the setting
	 *
	 * @return the double value of the setting, or zero (0.0) if not found
	 */
	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public double getDouble(String name) {
		return getDouble(name, 0.0d);
	}

	/**
	 * @param name
	 *            the name of the setting
	 * @param value
	 *            the value of the setting
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
	public int saveSetting(String name, String value) {
		Map<String, Object> args = new HashMap<String, Object>(2);
		args.put(Params.NAME, name);
		args.put(Params.VALUE, value);
		return jdbcDao.executeUpdate(SQL_INSERT_OR_UPDATE_SETTING, args);
	}

	/**
	 * Reset the settings cache
	 */
	public void resetSettingsCache() {
		// simply set the settings cache to null
		SETTINGS = null;
	}

}
