/*-
 * ~~~~~~licensing~~~~~~
 * connection-manager
 * ==========
 * 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.esb.platform.runtime.core.management.connection.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import ru.entaxy.esb.platform.runtime.base.connecting.connection.Connection;
import ru.entaxy.esb.platform.runtime.base.connecting.connection.util.ConnectionUtil;
import ru.entaxy.esb.platform.runtime.base.connecting.generator.Generated;
import ru.entaxy.esb.platform.runtime.base.connecting.producer.connection.util.ConnectionProducerUtil;
import ru.entaxy.esb.platform.runtime.core.management.connection.api.ConnectionManager;
import ru.entaxy.platform.core.artifact.Artifact;
import ru.entaxy.platform.core.artifact.ArtifactManifest;
import ru.entaxy.platform.core.artifact.Artifacts;
import ru.entaxy.platform.core.artifact.DeployedArtifact;
import ru.entaxy.platform.core.artifact.Manifested;
import ru.entaxy.platform.core.artifact.capabilities.ManifestCapabilityHelper;
import ru.entaxy.platform.core.artifact.installer.builder.Installer;
import ru.entaxy.platform.core.artifact.installer.builder.typed.BlueprintInstaller;
import ru.entaxy.platform.core.artifact.service.ArtifactService;

public class ConnectionManagerImpl implements ConnectionManager {

    private static final String CONNECTION = "connection";
/*    
	private static final String GROUP_ID = "ru.entaxy.esb.";
    private static final String XML_EXTENSION = "xml";
    private static final String LOCAL_FILE_REPOSITORY = "data" + File.separator + "local-repository";
*/    
    protected ArtifactService artifactService;
    
    private static final Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class);

    @Override
    public List<Connection> checkInstallConnections(String json, String timestamp) throws Exception {
    	List<Connection> connections = parseConnections(json);
    	for (Connection connection: connections) {

    		Artifact artifact = createArtifactForConnection(connection);
    		artifact.getCoordinates().timestamped(timestamp);

    		DeployedArtifact deployed;
    		if (connection.isPlatform()) {
    			deployed = artifactService.deployLocal(artifact);
    		} else {
    			deployed = artifactService.deployShared(artifact);
    		}
        	log.info("Artifact [{}] deployed to location [{}]", artifact.getCoordinates(), deployed.getLocation());

        	Installer<?> installer = connection.isPlatform() 
        			?artifactService.installers().local()
        			:artifactService.installers().cluster();

        	// TODO add support for other types
        	// assume artifact is blueprint
        	installer
        			.artifact(deployed)
        			.typed(BlueprintInstaller.class)
        			.installOnlyIfMissing()
        			.start()
        			.install();
        	
    	}
    	return connections;
    }
    
    @Override
    public List<Connection> installUpdateConnections(String json, String newTimestamp) throws Exception {
    	List<Connection> connections = parseConnections(json);
    	for (Connection connection: connections) {

    		Artifact artifact = createArtifactForConnection(connection);
    		artifact.getCoordinates().timestamped(newTimestamp);

    		DeployedArtifact deployed;
    		if (connection.isPlatform()) {
    			deployed = artifactService.deployLocal(artifact);
    		} else {
    			deployed = artifactService.deployShared(artifact);
    		}
        	log.info("Artifact [{}] deployed to location [{}]", artifact.getCoordinates(), deployed.getLocation());

        	Installer<?> installer = connection.isPlatform() 
        			?artifactService.installers().local()
        			:artifactService.installers().cluster();

        	// TODO add support for other types
        	// assume artifact is blueprint
        	installer
        			.artifact(deployed)
        			.typed(BlueprintInstaller.class)
        			.update(deployed
        					.getArtifact().getProperties()
        					.getOrDefault(Constants.BUNDLE_SYMBOLICNAME, "")
        					.toString())
        			.start()
        			.install();
        	
    	}
    	return connections;
    }
    
    protected List<Connection> parseConnections(String json){
        List<Connection> connectionList = new ArrayList<>();
        JsonElement jsonElement = getJsonElement(json);
        List<JsonObject> jsonConnectionList = getJsonConnectionList(jsonElement);
        for (JsonObject jsonConnection : jsonConnectionList) {
            connectionList.add(ConnectionProducerUtil.createConnection(jsonConnection));
        }
        return connectionList;
    }
    
    protected List<Artifact> createArtifactsForConnections(List<Connection> connections) throws Exception{
    	List<Artifact> artifacts = new ArrayList<>();
    	for (Connection connection: connections)
    		artifacts.add(createArtifactForConnection(connection));
    	return artifacts;
    }
    
    protected Artifact createArtifactForConnection(Connection connection) throws Exception{
    	 Generated result = ConnectionUtil.getInit(connection);
    	 String type = CONNECTION;
    	 String name = connection.getName();
    	 String version = "1.0.0";
    	 
    	 // TODO add support for other artifact types
         if (Generated.GENERATED_TYPE_BLUEPRINT.equals(result.getType())) {
         	
         	Artifact artifact = Artifacts.fromGenerated(result);
         	artifact.getCoordinates()
         		.timestampedVersion(version)
         		.groupId(Artifact.DEFAULT_RUNTIME_GROUP_ID + "." + type)
         		.artifactId(type + "-" + name);
         	
         	if (artifact.isManifested()) {
         		ArtifactManifest manifest = ((Manifested)artifact).getManifest();
         		ManifestCapabilityHelper capabilityHelper = new ManifestCapabilityHelper(manifest);
         		capabilityHelper.provideCapability(artifact.getCoordinates().getGroupId())
         			.attribute("name",connection.getName())
         			.attribute("platform", connection.isPlatform()?"true":"false");
         		capabilityHelper.save();
         	}
         	
         	log.info("Artifact of category [{}] is prepared: [{}]", artifact.getCategory(), artifact.getCoordinates());
         	return artifact;
         } else {
         	log.error("Generated result for connection [{}] is of type '{}' while expected '{}'"
        			, connection.getName()
        			, result.getType()
        			, Generated.GENERATED_TYPE_BLUEPRINT);
        	return null;
         }	
    }

/*    
    public List<Connection> createAndInstallConnections(String json) throws Exception {
        return createConnections(json, false, true);
    }

    private List<Connection> createConnections(String json) throws Exception {
        return createConnections(json, false, false);
    }

    private List<Connection> createConnections(String json, boolean register, boolean install) throws Exception {
        List<Connection> connectionList = new ArrayList<>();
        JsonElement jsonElement = getJsonElement(json);
        List<JsonObject> jsonConnectionList = getJsonConnectionList(jsonElement);
        for (JsonObject jsonConnection : jsonConnectionList) {
            connectionList.add(createConnection(jsonConnection, register, install));
        }
        return connectionList;
    }
*/
    private JsonElement getJsonElement(String json) {
        return new Gson().fromJson(json, JsonElement.class);
    }

    private List<JsonObject> getJsonConnectionList(JsonElement element) {
        List<JsonObject> jsonConnectionList = new ArrayList<>();

        if (element.isJsonObject()) {
            JsonObject object = element.getAsJsonObject();
            if (object.has("nodeType") &&
                    "connection".equals(object.get("nodeType").getAsString())) {
                jsonConnectionList.add(object);
            } else {
                for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
                    jsonConnectionList.addAll(getJsonConnectionList(entry.getValue()));
                }
            }
        } else if (element.isJsonArray()) {
            for (JsonElement jsonElement : element.getAsJsonArray()) {
                jsonConnectionList.addAll(getJsonConnectionList(jsonElement));
            }
        }

        return jsonConnectionList;
    }
/*
    private Connection createConnection(JsonObject jsonConnection, boolean register, boolean install) {
        Connection connection = ConnectionProducerUtil.createConnection(jsonConnection);
        if (register) {
            // TODO: 09.07.2021 registration method
        }
        if (install) {
            try {
                installConnection(connection);
            } catch (Exception e) {
                log.error("Can't install connection: {}", e.getMessage());
            }
        }
        return connection;
    }

    public Bundle installConnection(Connection connection) throws Exception {
        String name = connection.getName();
        String type = CONNECTION;
        String version = String.valueOf(1.0);
        Generated result = ConnectionUtil.getInit(connection);
        
        // TODO: pass the Generated to artifact management
        
        if (Generated.GENERATED_TYPE_BLUEPRINT.equals(result.getType())) {
        	
        	Artifact artifact = Artifacts.fromGenerated(result);
        	artifact.getCoordinates()
        		.timestampedVersion(version)
        		.groupId(Artifact.DEFAULT_RUNTIME_GROUP_ID + "." + type)
        		.artifactId(type + "-" + name);
        	log.info("Artifact of category [{}] is prepared: [{}]", artifact.getCategory(), artifact.getCoordinates());

        	DeployedArtifact deployedArtifact = artifactService.deployLocal(artifact);
        	log.info("Artifact [{}] deployed to location [{}]", artifact.getCoordinates(), deployedArtifact.getLocation());
        	
        	InstallationResult installationResult =  this.artifactService
        		.installers()
        		.cluster()
        		.artifact(deployedArtifact)
        		.typed(BlueprintInstaller.class)
        		.start()
        		.update(deployedArtifact.getArtifact().getProperties().getOrDefault(Constants.BUNDLE_SYMBOLICNAME, "").toString())
        		.install();
*/        		
/*        	
            log.debug(new String(result.getObject().toString().getBytes(), Charset.defaultCharset()));
            log.info("Blueprint is generated for connection [{}]", connection.getName());
            
            // Local bundle install needed instead of cellar install to cluster
            String bundleUrl = deployBlueprint(GROUP_ID + type, type + "-" + name,
                  version, XML_EXTENSION, result.getObject().toString().getBytes());
            Bundle bundle = installAndStartBundle(bundleUrl);
*/
/*        	if (installationResult.isSuccessful()) {
        		if (installationResult.getObject() != null)
        			if (installationResult.getObject() instanceof Bundle) {
		        		Bundle bundle = (Bundle)installationResult.getObject(); 
		        		log.info("Bundle [{}] successfully {}'d from artifact [{}]"
		        				, bundle.getBundleId()
		        				, installationResult.getResult().name()
		        				, deployedArtifact.getArtifact().getCoordinates());
		        		return bundle;
        			}
        	}
        	return null;
            // TODO: 09.07.2021  callback for webeditor needed
        } else {
        	log.error("Generated result for connection [{}] is of type '{}' while expected '{}'"
        			, connection.getName()
        			, result.getType()
        			, Generated.GENERATED_TYPE_BLUEPRINT);
        	return null;
        }
    }

    // ru.entaxy.esb.system.deployer.file.FileSystemRepositoryImpl.deployBlueprint method copy
    @Deprecated
    public String deployBlueprint(String groupId, String name, String version,
                                  String extension, byte[] data) throws Exception {
        Path path = preparePath(groupId, name, version, extension);
        if (!Files.exists(path.getParent())) {
            Files.createDirectories(path.getParent());
        }
        Files.write(path, data);
        return "blueprint:file:" + path.normalize();
    }

    @Deprecated
    private Path preparePath(String groupId, String name, String version, String extension) {
        String gpoupIdPath = groupId.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
        String fileName = name + "-" + version + "." + extension;
        Path path = Paths.get(LOCAL_FILE_REPOSITORY, gpoupIdPath, fileName);
        return path;
    }
    
    @Deprecated
    private Bundle installAndStartBundle(String bundleUrl) {
        BundleContext bundleContext = FrameworkUtil.getBundle(ConnectionManagerImpl.class).getBundleContext();
        Bundle bundle = null;
        try {
            bundle = bundleContext.installBundle(bundleUrl, null);
            try {
                bundle.start();
            } catch (BundleException e) {
                log.error("Unable to start bundle " + bundle.getLocation() + ": " + e, e);
            }
        } catch (BundleException e) {
            log.error("Unable to install bundle " + bundle.getLocation() + ": " + e, e);
        }
        return bundle;
    }
*/
	public void setArtifactService(ArtifactService artifactService) {
		this.artifactService = artifactService;
	}
    
}
