/*
 * Copyright (c) 2017 Stamina Framework developers.
 *
 * 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 io.staminaframework.subsystem.internal;

import org.apache.felix.fileinstall.ArtifactInstaller;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.log.LogService;
import org.osgi.service.subsystem.Subsystem;
import org.osgi.service.subsystem.SubsystemConstants;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * OSGi Subsystem installer.
 *
 * @author Stamina Framework developers
 */
@Component(service = ArtifactInstaller.class, immediate = true)
public class SubsystemInstaller implements ArtifactInstaller {
    @Reference
    private LogService logService;
    @Reference(target = "(" + SubsystemConstants.SUBSYSTEM_ID_PROPERTY + "=0)")
    private Subsystem root;
    private BundleContext bundleContext;

    @Activate
    public void activate(BundleContext bundleContext) throws Exception {
        this.bundleContext = bundleContext;
    }

    @Deactivate
    public void deactivate() throws IOException {
        bundleContext = null;
    }

    @Override
    public void install(File artifact) throws Exception {
        final Manifest man = getManifest(artifact);
        final String sid = getSubsystemId(man);

        final String spath = artifact.getCanonicalFile().toURI().toURL().toExternalForm();
        final boolean alreadyInstalled = bundleContext.getBundle(spath) != null;
        if (alreadyInstalled) {
            logService.log(LogService.LOG_DEBUG, "Subsystem " + sid + " is already installed");
            return;
        }

        logService.log(LogService.LOG_INFO, "Installing subsystem: " + sid);
        final Subsystem sub = root.install(spath);
        logService.log(LogService.LOG_INFO, "Starting subsystem: " + sid);
        sub.start();
    }

    @Override
    public void update(File artifact) throws Exception {
        final Manifest man = getManifest(artifact);
        final String sid = getSubsystemId(man);
        logService.log(LogService.LOG_INFO, "Updating subsystem: " + sid);
        uninstall(artifact);
        install(artifact);
        logService.log(LogService.LOG_INFO, "Subsystem updated: " + sid);
    }

    @Override
    public void uninstall(File artifact) throws Exception {
        final String spath = artifact.getCanonicalFile().toURI().toURL().toExternalForm();
        // Subsystem is already installed: we need to uninstall it first.
        for (final Subsystem sub : root.getChildren()) {
            if (spath.equals(sub.getLocation())) {
                final String sid = getSubsystemId(sub);
                logService.log(LogService.LOG_INFO, "Uninstalling subsystem: " + sid);
                sub.uninstall();
                logService.log(LogService.LOG_INFO, "Subsystem uninstalled: " + sid);
            }
        }
    }

    @Override
    public boolean canHandle(File artifact) {
        if (!artifact.getName().toLowerCase().endsWith(".esa")) {
            return false;
        }
        try {
            final Manifest man = getManifest(artifact);
            final String ssn = man.getMainAttributes().getValue(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME);
            if (ssn == null) {
                throw new IOException("Missing symbolic name in subsystem manifest");
            }
        } catch (IOException e) {
            logService.log(LogService.LOG_WARNING, "Failed to open file as a subsystem: " + artifact, e);
            return false;
        }
        return true;
    }

    private Manifest getManifest(File artifact) throws IOException {
        final Manifest man = new Manifest();
        try (final ZipFile zip = new ZipFile(artifact)) {
            final ZipEntry manEntry = zip.getEntry("OSGI-INF/SUBSYSTEM.MF");
            if (manEntry == null) {
                throw new IOException("Missing subsystem manifest");
            }
            try (final InputStream manIn = zip.getInputStream(manEntry)) {
                man.read(manIn);
            }
            final Attributes atts = man.getMainAttributes();
            final String ssn = atts.getValue(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME);
            if (ssn == null) {
                throw new IOException("Missing symbolic name in subsystem manifest");
            }
        }
        return man;
    }

    private String getSubsystemId(Manifest man) {
        final String ssn = man.getMainAttributes().getValue(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME);
        String svn = man.getMainAttributes().getValue(SubsystemConstants.SUBSYSTEM_VERSION);
        if (svn == null) {
            svn = "0.0.0";
        }
        return ssn + "/" + svn;
    }

    private String getSubsystemId(Subsystem sub) {
        final String ssn = sub.getSymbolicName();
        final String svn = sub.getVersion().toString();
        return ssn + "/" + svn;
    }
}

@@ -1917,38 +1917,19 @@
 tivate()
- throws IOException
  %7B%0A
+
         
@@ -2266,32 +2266,30 @@
 d = 
-bundleContext.getBundle(
+lookupSubsystem(root, 
 spat
@@ -2682,32 +2682,385 @@
 start();%0A    %7D%0A%0A
+    private Subsystem lookupSubsystem(Subsystem parent, String url) %7B%0A        if (parent.getLocation().equals(url)) %7B%0A            return parent;%0A        %7D%0A        for (final Subsystem child : parent.getChildren()) %7B%0A            if (lookupSubsystem(child, url) != null) %7B%0A                return child;%0A            %7D%0A        %7D%0A        return null;%0A    %7D%0A%0A
     @Override%0A  
@@ -3665,37 +3665,32 @@
  first.%0A        
-for (
 final Subsystem 
@@ -3696,42 +3696,55 @@
  sub
- : root.getChildren()) %7B%0A 
+system = lookupSubsystem(root, spath);%0A
         
    i
@@ -3743,54 +3743,33 @@
     
-   
 if (s
-path.equals(sub.getLocation())) %7B%0A    
+ubsystem != null) %7B%0A
     
@@ -3813,16 +3813,22 @@
 emId(sub
+system
 );%0A     
@@ -3826,36 +3826,32 @@
 m);%0A            
-    
 logService.log(L
@@ -3917,23 +3917,25 @@
         
-    sub
+subsystem
 .uninsta
@@ -3944,36 +3944,32 @@
 ();%0A            
-  
-  
 logService.log(L
@@ -4022,30 +4022,16 @@
 + sid);%0A
-            %7D%0A
         

Fix subsystem install