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

import be.iminds.ilabt.jfed.lowlevel.authority.binding.Authorities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.bind.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * StoredAuthorityList
 */
@SuppressWarnings("deprecation")
@Deprecated
public class LegacyStoredAuthorityList {
    private static final Logger LOG = LoggerFactory.getLogger(LegacyStoredAuthorityList.class);

    /**
     * returns whether successfully loaded. returns false if no file to load.
     */
    public static LoadResult load(String content, LegacyAuthorityListModel authorityListModel) {
        try {
            InputStream sis = new ByteArrayInputStream(content.getBytes("UTF-8"));
            return load(sis, authorityListModel);
        } catch (UnsupportedEncodingException e) {
            LOG.error("UnsupportedEncodingException", e);
            return new LoadResult(false);
        }
    }

    public static class LoadResult {
        private final boolean success;
        private final List<String> extraFiles;

        public LoadResult(boolean success, List<String> extraFiles) {
            this.success = success;
            this.extraFiles = extraFiles;
        }
        public LoadResult(boolean success) {
            this(success, Collections.emptyList());
        }

        public boolean isSuccess() {
            return success;
        }

        public List<String> getExtraFiles() {
            return extraFiles;
        }
    }

    /**
     * returns whether successfully loaded. returns false if no file to load.
     */
    public static LoadResult load(File file, LegacyAuthorityListModel authorityListModel) {
        //not important if it doesn't exist
        if (!file.exists()) return new LoadResult(false);

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

    /**
     * returns whether successfully loaded.
     */
    public static LoadResult load(InputStream is, LegacyAuthorityListModel authorityListModel) {
        List<String> extraFiles = new ArrayList<>();
        try {
            Class docClass = be.iminds.ilabt.jfed.lowlevel.authority.binding.Authorities.class;
            String packageName = docClass.getPackage().getName();
            JAXBContext jc = JAXBContext.newInstance(packageName);
            Unmarshaller u = jc.createUnmarshaller();
//            JAXBElement<Authorities> doc =
//                    (JAXBElement<Authorities>) u.unmarshal(file);
//            be.iminds.ilabt.jfed.lowlevel.authority.binding.Authorities c = doc.getValue();
            be.iminds.ilabt.jfed.lowlevel.authority.binding.Authorities c = (Authorities) u.unmarshal(is);

            List<LegacySfaAuthority> authorities = new ArrayList<>();
            for (Authorities.Authority xmlAuthority : c.getAuthority()) {
                LegacySfaAuthority sfaAuthority = LegacySfaAuthority.fromXml(xmlAuthority);

                //can return null if error in authority
                if (sfaAuthority == null)
                    continue;

                //find existing
                LegacySfaAuthority existingSfaAuthority =
                        authorityListModel.getByUrnExact(sfaAuthority.getUrnString());
                if (existingSfaAuthority == null) {
                    authorityListModel.addAuthority(sfaAuthority);
                    authorities.add(sfaAuthority);
                }
                else {
                    boolean replace = false;

                    if (existingSfaAuthority.isWasStored()) replace = true;
                    //TODO handle it more intelligently? (example: compare with source of saved, and let for example USER_PROVIDED always take priority?)
                    if (existingSfaAuthority.getSource() != null) {
                        switch (existingSfaAuthority.getSource()) {
                            case BUILTIN:
                                replace = true;
                                break;
                            case USER_PROVIDED:
                                break;
                            case UTAH_CLEARINGHOUSE:
                                break;
                        }
                    }

                    if (replace) {
                        authorityListModel.removeByUrn(sfaAuthority.getUrnString());
                        authorityListModel.addAuthority(sfaAuthority);
                        authorities.add(sfaAuthority);
                    } else {
                        //update
                        authorities.add(authorityListModel.mergeOrAdd(sfaAuthority));
                    }
                }
            }

            for (Authorities.Type authorityType : c.getType()) {
                if (authorityType.getName() == null || authorityType.getName().trim().isEmpty()) continue;
                for (String specialId : authorityType.getSpecial()) {
                    try {
                        if (specialId == null || specialId.trim().isEmpty()) continue;
                        LegacySfaAuthority.SpecialCase specialCase = LegacySfaAuthority.SpecialCase.fromString(specialId.trim());
                        authorities.stream().filter(auth -> auth.getType() != null &&
                                auth.getType().equalsIgnoreCase(authorityType.getName()))
                                .forEach(auth -> auth.setSpecialCase(specialCase, true));
                    } catch (IllegalArgumentException e) {
                        LOG.warn("Parsed authority XML contained unknown <special> option: \""+specialId+"\"");
                    }
                }
            }

            int curVersion = 1;
            for (Authorities.ExtraFile extraFile : c.getExtraFile()) {
                if (extraFile.getMinVersion().intValue() <= curVersion) {
                    extraFiles.add(extraFile.getUrl());
                } else {
                    LOG.debug("Ignoring extrafile for version "+extraFile.getMinVersion());
                }
            }
        } catch (Exception e) {
            LOG.warn("Error reading authorities from XML: ", e);
            return new LoadResult(false);
        } finally {
            try {
                is.close();
            } catch (IOException e) { /*ignore exceptions when closing*/ }
        }
        return new LoadResult(true, extraFiles);
    }

    public static void save(File file, LegacyAuthorityListModel authorityListModel) {
        try {
            //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;
            }

            //write to file
            FileWriter writer = new FileWriter(file);

            toXml(writer, authorityListModel.getAuthorities(), false, false);

            writer.close();
        } catch (IOException e) {
            LOG.warn("Error writing authorities to XML file \"" + file.getName() + "\": ", e);
        }
    }

    public static String toXml(LegacyAuthorityListModel authorityListModel) {
        return toXml(authorityListModel.getAuthorities(), false, false);
    }
    public static String toXml(List<LegacySfaAuthority> authorityList, boolean omitDeclaration, boolean noNamespace) {
        try {
            StringWriter stringWriter = new StringWriter();
            toXml(stringWriter, authorityList, omitDeclaration, noNamespace);
            stringWriter.close();
            return stringWriter.toString();
        } catch (Exception e) {
            LOG.warn("WARNING: Error writing authorities to XML: ", e);
        }
        return null;
    }

    public static void toXml(Writer outputWriter, List<LegacySfaAuthority> authorityList, boolean omitDeclaration, boolean noNamespace) {
        try {
            //nonamespace doesn't work

            be.iminds.ilabt.jfed.lowlevel.authority.binding.Authorities xmlAuthorities =
                    new be.iminds.ilabt.jfed.lowlevel.authority.binding.Authorities();
            for (LegacySfaAuthority auth : authorityList) {
                be.iminds.ilabt.jfed.lowlevel.authority.binding.Authorities.Authority xmlAuth = auth.toXml();
                xmlAuthorities.getAuthority().add(xmlAuth);
            }

            JAXBContext context =
                    JAXBContext.newInstance(be.iminds.ilabt.jfed.lowlevel.authority.binding.Authorities.class);
            Marshaller m = context.createMarshaller();
            if (omitDeclaration) {
                m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
            }
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

            m.marshal(xmlAuthorities, outputWriter);
        } catch (JAXBException e) {
            LOG.warn("WARNING: Error writing authorities: ", e);
        }
    }
}
