package be.iminds.ilabt.jfed.lowlevel.authority;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Testbed;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.TestbedBuilder;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.util.jsonld.JsonLdObjectsMetaData;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.dropwizard.jackson.Jackson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;

/**
 * StoredAuthorityList
 */
public class StoredTestbedInfo {
    private static final Logger LOG = LoggerFactory.getLogger(StoredTestbedInfo.class);

    private static final ObjectMapper MAPPER = Jackson.newObjectMapper();
    static {
        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * @return null if not successfully loaded
     */
    public static List<Testbed> loadOrNull(String content) {
        try {
            InputStream sis = new ByteArrayInputStream(content.getBytes("UTF-8"));
            return loadOrNull(sis);
        } catch (UnsupportedEncodingException e) {
            LOG.error("UnsupportedEncodingException", e);
            return null;
        }
    }

    /**
     * @return null if not successfully loaded
     */
    public static List<Testbed> loadOrNull(File file) {
        //not important if it doesn't exist
        if (!file.exists()) return null;

        try {
            return load(new FileInputStream(file));
        } catch (IOException e) {
            LOG.warn("Error reading authorities from XML file \"" + file + "\": ", e);
            return null;
        }
    }

    public static List<Testbed> load(File file) throws IOException {
        if (!file.exists()) throw new FileNotFoundException("File \""+file.getPath()+"\" not found");

        return load(new FileInputStream(file));
    }

    /**
     * @return null if not successfully loaded
     */
    public static List<Testbed> loadOrNull(InputStream is) {
        try {
            return load(is);
        } catch (IOException e) {
            LOG.error("Failed to process List<Testbed> Json", e);
            return null;
        }
    }

    public static List<Testbed> load(InputStream is) throws IOException {
//        List<Testbed> res = MAPPER.readValue(is, MAPPER.getTypeFactory().constructCollectionType(List.class, Testbed.class));
//        return res;

        Object inputObject = MAPPER.readValue(is, Object.class);

        //this should work for BOTH a collection of Testbed, and for a single Testbed

        if (inputObject instanceof Collection) {
            ArrayList<Testbed> resList = new ArrayList<>();
            for (Object o : (Collection)inputObject) {
                Testbed testbed = MAPPER.convertValue(o, Testbed.class);
                resList.add(testbed);
            }
            return resList;
        }
        if (inputObject instanceof Map) {
            Testbed testbed = MAPPER.convertValue(inputObject, Testbed.class);
            return Collections.singletonList(testbed);
        }

        throw new RuntimeException("Unknown object read: \""+inputObject.getClass().getName()+"\"");
    }

    public static void saveIgnoringErrors(File file, TestbedInfoSource testbedInfoSource) {
        try {
            save(file, testbedInfoSource);
        } catch (IOException e) {
            LOG.warn("Error writing authorities to XML file \"" + file.getName() + "\": ", e);
        }
    }
    public static void save(File file, TestbedInfoSource testbedInfoSource) throws IOException {
        List<Testbed> minimizedTestbeds = TestbedBuilder.minimize(JsonLdObjectsMetaData.Minimization.FULL_EMBEDDING_PARENT_AND_CHILDREN, testbedInfoSource.getTestbeds());

        //first see if the dir exists, and try to create it if not.
        File dir = file.getParentFile();
        if (!dir.exists()) //noinspection ResultOfMethodCallIgnored
            dir.mkdir();
        if (!dir.exists()) {
            LOG.warn("could not save authorities to \"" + file.getName() + "\", as dir \"" + dir.getName() + "\" could not be created.");
            return;
        }

        MAPPER.writer().with(SerializationFeature.INDENT_OUTPUT).writeValue(file, minimizedTestbeds);
    }

    public static String toJson(TestbedInfoSource testbedInfoSource) {
        return toJson(testbedInfoSource.getTestbeds());
    }
    public static String toJson(List<Testbed> testbeds) {
        try {
            List<Testbed> minimizedTestbeds = TestbedBuilder.minimize(JsonLdObjectsMetaData.Minimization.FULL_EMBEDDING_PARENT_AND_CHILDREN, testbeds);
            String json = MAPPER.writer().with(SerializationFeature.INDENT_OUTPUT).writeValueAsString(minimizedTestbeds);
            return json;
        } catch (Exception e) {
            LOG.warn("WARNING: Error writing Testbeds to JSON: ", e);
            return null;
        }
    }

    public static void toJson(Writer outputWriter, List<Testbed> testbeds) {
        try {
            List<Testbed> minimizedTestbeds = TestbedBuilder.minimize(JsonLdObjectsMetaData.Minimization.FULL_EMBEDDING_PARENT_AND_CHILDREN, testbeds);
            MAPPER.writer().with(SerializationFeature.INDENT_OUTPUT).writeValue(outputWriter, minimizedTestbeds);
        } catch (Exception e) {
            LOG.warn("WARNING: Error writing Testbeds to JSON: ", e);
        }
    }
}
