package io.cattle.platform.docker.machine.launch;

import static io.cattle.platform.core.model.tables.MachineDriverTable.*;

import io.cattle.platform.archaius.util.ArchaiusUtil;
import io.cattle.platform.core.model.MachineDriver;
import io.cattle.platform.core.model.tables.records.MachineDriverRecord;
import io.cattle.platform.json.JsonMapper;
import io.cattle.platform.lock.LockCallbackNoReturn;
import io.cattle.platform.lock.LockManager;
import io.cattle.platform.object.ObjectManager;
import io.cattle.platform.object.process.ObjectProcessManager;
import io.cattle.platform.object.process.StandardProcess;
import io.cattle.platform.util.type.InitializationTask;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.config.DynamicStringProperty;

public class MachineDriverLoader implements InitializationTask {

    public static final DynamicStringProperty CONFIG_FILE = ArchaiusUtil.getString("machine.driver.config");

    private static final Logger log = LoggerFactory.getLogger(MachineDriverLoader.class);

    @Inject
    LockManager lockManager;
    @Inject
    ObjectManager objectManager;
    @Inject
    ObjectProcessManager processManager;
    @Inject
    JsonMapper jsonMapper;

    @Override
    public void start() {
        // do logic once
        CONFIG_FILE.addCallback(new Runnable() {
            @Override
            public void run() {
                loadDrivers();
            }
        });
        try {
            loadDrivers();
        } catch (Exception e) {
            log.error("Failed to load drivers.", e);
        }
    }

    public void loadDrivers() {
        lockManager.lock(new MachineDriverLoaderLock(), new LockCallbackNoReturn() {
            @Override
            public void doWithLockNoResult() {
                String configLocation = CONFIG_FILE.get();
                if (StringUtils.isBlank(configLocation)) {
                    return;
                }

                InputStream in = null;
                byte[] configJson = null;
                DriverConfig dc = null;
                try {
                    URL configUrl = new URL(configLocation);
                    in = configUrl.openStream();
                    configJson = IOUtils.toByteArray(in);
                    dc = jsonMapper.readValue(configJson, DriverConfig.class);
                } catch (FileNotFoundException e) {
                    log.info("Not loading [{}], file is not found", configLocation);
                    return;
                } catch (Exception e) {
                    log.error("Error while reading machine driver config json.", e);
                    return;
                } finally {
                    IOUtils.closeQuietly(in);
                }

                if (dc.getDrivers() == null || dc.getDrivers().isEmpty()) {
                    return;
                }

                List<MachineDriver> driverList = objectManager.find(MachineDriver.class, MACHINE_DRIVER.REMOVED, (Object)null);
                Map<String, MachineDriver> existingDrivers = new HashMap<>();
                for (MachineDriver d : driverList) {
                    existingDrivers.put(d.getName(), d);
                }

                for (MachineDriver newDriver : dc.getDrivers()) {
                    if (StringUtils.isBlank(newDriver.getName()) || StringUtils.isBlank(newDriver.getUri())) {
                        log.error("Machine driver invalid. Name and uri must not be blank. Name: {}, Uri: {}", newDriver.getName(), newDriver.getUri());
                        continue;
                    }

                    if (existingDrivers.containsKey(newDriver.getName())) {
                        MachineDriver existing = existingDrivers.get(newDriver.getName());
                        if (!StringUtils.equals(existing.getMd5checksum(), newDriver.getMd5checksum())
                                || !StringUtils.equals(existing.getUri(), newDriver.getUri())) {
                            processManager.executeStandardProcess(StandardProcess.REMOVE, existing, null);
                        } else {
                            continue;
                        }
                    }
                    log.info("Loading machine driver with name {}, uri {}, checksum {}", newDriver.getName(), newDriver.getUri(), newDriver.getMd5checksum());
                    objectManager.create(newDriver);
                    processManager.scheduleStandardProcess(StandardProcess.CREATE, newDriver, null);
                }
            }
        });
    }

}

class DriverConfig {
    List<MachineDriverRecord> drivers;

    public List<MachineDriverRecord> getDrivers() {
        return drivers;
    }

    public void setDrivers(List<MachineDriverRecord> drivers) {
        this.drivers = drivers;
    }
}
