/*-
 * ~~~~~~licensing~~~~~~
 * artifact-management
 * ==========
 * Copyright (C) 2020 - 2021 EmDev LLC
 * ==========
 * 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.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.core.artifact.installer.builder.impl.cluster;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;

import org.apache.karaf.cellar.bundle.management.CellarBundleMBean;
import org.osgi.framework.Constants;

import ru.entaxy.esb.system.common.exception.BundleNotFound;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.base.support.osgi.OSGIUtils;
import ru.entaxy.platform.core.artifact.installer.builder.InstallationResult;
import ru.entaxy.platform.core.artifact.installer.builder.impl.InstallationResultImpl;
import ru.entaxy.platform.core.artifact.installer.builder.typed.impl.CommonBundleInstallerHelper;
import ru.entaxy.platform.core.artifact.installer.builder.typed.impl.CommonBundleInstallerHelperConfig;

public class ClusterCommonBundleInstallerHelper extends ClusterTypedInstallerHelper
		implements CommonBundleInstallerHelper {

	protected CommonBundleInstallerHelperConfig config;
	
	@Override
	public void setConfig(CommonBundleInstallerHelperConfig config) {
		this.config = config;
	}

	@Override
	public InstallationResultImpl install(InstallationResultImpl installationResult) {
		//ClusterTypedInstallerHelper _helper = (ClusterTypedInstallerHelper)helper; 
		
//		InstallationResultImpl installationResult = InstallationResultImpl
//				.create()
//				.property("artifact", artifact);

		CompositeData existingBundle = null;
		
		List<String> groupsToInstall = new ArrayList<>();
		for (String groupName: getGroups())
			if (checkGroup(groupName))
				if (!groupsToInstall.contains(groupName))
					groupsToInstall.add(groupName);
		
		if (groupsToInstall.size()==0) {
			return installationResult.failed("No groups to install to");
		}
		
		CellarBundleMBean bundleService = OSGIUtils.getService(CellarBundleMBean.class);

		if (bundleService == null) {
			// TODO throw exception
			return installationResult
					.failed("Can't install/update: CellarBundleMBean not available");
		} 

		// TODO decide if we really need several groups
		// now we use only the first one
		String groupToInstall = groupsToInstall.get(0);
		
		if (this.config.isUpdate() || this.config.isUpdateOnly() || this.config.isInstallOnlyIfMissing()
				|| this.config.isStartBundleOnly() || this.config.isStopBundleOnly()) {
			
			
			String bundleId = this.config.getBundleId();
			
			if (!CommonUtils.isValid(bundleId)) {
				// TODO throw exception
				return installationResult
						.failed("Can't update: bundleId can not be defined");
			}
			
			// if not bundles found BundleService throws exception
			try {
				existingBundle = getBundleData(bundleId, bundleService, groupToInstall);
			} catch (Exception e) {
				// NOOP
			}
			
			if (existingBundle != null) {
				
				installationResult.object(existingBundle);

				if (this.config.isInstallOnlyIfMissing()) {
					
					installationResult.notChanged();
					
				} else 

				if (this.config.isUpdate() || this.config.isUpdateOnly()) {
				
					try {
						if (this.config.isStartBundle())
							bundleService.updateStart(groupToInstall, bundleId, this.config.getBundleLocation());
						else
							bundleService.update(groupToInstall, bundleId, this.config.getBundleLocation());
							
						installationResult.updated();
					} catch (Exception e) {
						installationResult.failed().error(e);
					}
				} else
					
				if (this.config.isStartBundleOnly()) {
					
					try {
						bundleService.start(groupToInstall, bundleId);
						installationResult.started()
							.property(InstallationResult.PROP_BUNDLE_STATUS
									, getStatusByName(bundleId, bundleService, groupToInstall));
					} catch (Exception e) {
						// TODO Auto-generated catch block
						installationResult.failed().error(e);
					}
					
				} else 
					
				if (this.config.isStopBundleOnly()) {

					try {
						bundleService.stop(groupToInstall, bundleId);
						installationResult.stopped()
							.property(InstallationResult.PROP_BUNDLE_STATUS
								, getStatusByName(bundleId, bundleService, groupToInstall));
					} catch (Exception e) {
						// TODO Auto-generated catch block
						installationResult.failed().error(e);
					}

				}	
				
				return installationResult;
			}
		}

		if (this.config.isUpdateOnly()) {
			return installationResult.failed("UpdateOnly failed: bundle to update not found");
		}
		
		try {
			if (this.config.getStartLevel() > 0) {
				bundleService.install(groupToInstall, this.config.getBundleLocation()
						, this.config.getStartLevel(), this.config.isStartBundle());
			} else {
				bundleService.install(groupToInstall, this.config.getBundleLocation(), this.config.isStartBundle());
			}
			installationResult.installed()
				.property(InstallationResult.PROP_BUNDLE_STATUS
						, getStatusByLocation(this.config.getBundleLocation(), bundleService, groupToInstall));
		} catch (Exception e) {
			installationResult.error(e)
				.failed("Installing bundle failed: Exception");
		}
		
		return installationResult.installed();
	}

	@Override
	public InstallationResultImpl uninstall(InstallationResultImpl installationResult) {
		CompositeData existingBundle = null;
		
		List<String> groupsToInstall = new ArrayList<>();
		for (String groupName: getGroups())
			if (checkGroup(groupName))
				if (!groupsToInstall.contains(groupName))
					groupsToInstall.add(groupName);
		
		if (groupsToInstall.size()==0) {
			return installationResult.failed("No groups to install to");
		}
		
		CellarBundleMBean bundleService = OSGIUtils.getService(CellarBundleMBean.class);

		if (bundleService == null) {
			// TODO throw exception
			return installationResult
					.failed("Can't install/update: CellarBundleMBean not available");
		} 

		// TODO decide if we really need several groups
		// now we use only the first one
		String groupToInstall = groupsToInstall.get(0);
		String bundleId = this.config.getBundleId();
		
		if (!CommonUtils.isValid(bundleId)) {
			// TODO throw exception
			return installationResult
					.failed("Can't update: bundleId can not be defined");
		}
		
		// if not bundles found BundleService throws exception
		try {
			existingBundle = getBundleData(bundleId, bundleService, groupToInstall);
		} catch (Exception e) {
			// NOOP
		}
		
		if (existingBundle != null) {
			try {
				bundleService.uninstall(groupToInstall, bundleId);
				installationResult.uninstalled();
			} catch (Exception e) {
				installationResult.failed("Failed uninstalling bundle [" + bundleId + "]").error(e);
			}
		} else {
			installationResult.notChanged().message("BUNDLE NOT FOUND");
		}
		return installationResult;
	}
	
    private String getStatusByLocation(String bundleLocation
    		, CellarBundleMBean cellarBundleMBean
    		, String cellarGroup) throws Exception {
        TabularData tabularData = cellarBundleMBean.getBundles(cellarGroup);
        String status = null;
        for (Object value : tabularData.values()) {
            CompositeData compositeData = (CompositeData) value;
            if (bundleLocation.equals(compositeData.get("location"))) {
                status = compositeData.get("status").toString();
            }
        }
        if (status == null) {
            throw new BundleNotFound();
        }
        return status.toUpperCase();
    }

    private String getStatusByName(String bundleName
    		, CellarBundleMBean cellarBundleMBean
    		, String cellarGroup) throws Exception {
    	
    	CompositeData data = getBundleData(bundleName, cellarBundleMBean, cellarGroup);
    	if ( (data != null) && (data.containsKey("status"))) {
    		Object obj = data.get("status");
    		return obj == null?null:obj.toString(); 
    	};
    	return null;
    }
	
	
    private List<CompositeData> getBundlesData(String symbolicName
    		, CellarBundleMBean cellarBundleMBean
    		, String cellarGroup) throws Exception {
        TabularData tabularData = cellarBundleMBean.getBundles(cellarGroup);
        List<CompositeData> results = new LinkedList<>();
        for (Object value : tabularData.values()) {
            CompositeData compositeData = (CompositeData) value;
            if (symbolicName.equals(compositeData.get("name")) || symbolicName.equals(compositeData.get("symbolic_name"))) {
                results.add(compositeData);
            }
        }
        return results;
    }

    private CompositeData getBundleData(String symbolicName
    		, CellarBundleMBean cellarBundleMBean
    		, String cellarGroup) throws Exception {
    	List<CompositeData> results = getBundlesData(symbolicName, cellarBundleMBean, cellarGroup);
        if (results.size()==0)
        	return null;
        else
        	return results.get(0);
    }	
	
}
