package org.commcare.suite.model;

import org.commcare.core.parse.UserXmlParser;
import org.commcare.data.xml.DataModelPullParser;
import org.commcare.data.xml.TransactionParser;
import org.commcare.data.xml.TransactionParserFactory;
import org.commcare.xml.CommCareElementParser;
import org.javarosa.core.io.StreamsUtil;
import org.javarosa.core.model.User;
import org.javarosa.core.reference.InvalidReferenceException;
import org.javarosa.core.reference.Reference;
import org.javarosa.core.reference.ReferenceManager;
import org.javarosa.core.services.storage.Persistable;
import org.javarosa.core.util.PropertyUtils;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.PrototypeFactory;
import org.javarosa.xml.util.InvalidStructureException;
import org.javarosa.xml.util.UnfullfilledRequirementsException;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

/**
 * User restore xml file sometimes present in apps.
 * Used for offline (demo user) logins.
 *
 * @author Phillip Mates (pmates@dimagi.com)
 * @author Aliza Stone (astone@dimagi.com)
 */
public class OfflineUserRestore implements Persistable {
    public static final String STORAGE_KEY = "OfflineUserRestore";
    private int recordId = -1;
    private String restore;
    private String reference;
    private String username;
    private String password;

    public OfflineUserRestore() {
    }

    public OfflineUserRestore(String reference) throws UnfullfilledRequirementsException, IOException, InvalidStructureException,
            XmlPullParserException, InvalidReferenceException {

        this.reference = reference;
        checkThatRestoreIsValid(reference);
        this.password = PropertyUtils.genUUID();
    }

    public static OfflineUserRestore buildInMemoryUserRestore(InputStream restoreStream)
            throws UnfullfilledRequirementsException, IOException, InvalidStructureException,
            XmlPullParserException {

        OfflineUserRestore offlineUserRestore = new OfflineUserRestore();
        byte[] restoreBytes = StreamsUtil.inputStreamToByteArray(restoreStream);
        offlineUserRestore.restore = new String(restoreBytes);
        offlineUserRestore.checkThatRestoreIsValid(new ByteArrayInputStream(restoreBytes));
        offlineUserRestore.password = PropertyUtils.genUUID();

        return offlineUserRestore;
    }

    public InputStream getRestoreStream() {
        if (reference == null) {
            return getInMemoryStream();
        } else {
            return getStreamFromReference();
        }
    }

    private InputStream getInMemoryStream() {
        try {
            return new ByteArrayInputStream(restore.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    private InputStream getStreamFromReference() {
        try {
            Reference local = ReferenceManager._().DeriveReference(reference);
            return local.getStream();
        } catch (IOException | InvalidReferenceException e) {
            throw new RuntimeException(e);
        }
    }

    public String getReference() {
        return reference;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    @Override
    public void readExternal(DataInputStream in, PrototypeFactory pf)
            throws IOException, DeserializationException {
        this.recordId = ExtUtil.readInt(in);
        this.reference = ExtUtil.nullIfEmpty(ExtUtil.readString(in));
        this.restore = ExtUtil.nullIfEmpty(ExtUtil.readString(in));
        this.username = ExtUtil.nullIfEmpty(ExtUtil.readString(in));
        this.password = ExtUtil.nullIfEmpty(ExtUtil.readString(in));
    }

    @Override
    public void writeExternal(DataOutputStream out) throws IOException {
        ExtUtil.writeNumeric(out, recordId);
        ExtUtil.writeString(out, ExtUtil.emptyIfNull(reference));
        ExtUtil.writeString(out, ExtUtil.emptyIfNull(restore));
        ExtUtil.writeString(out, ExtUtil.emptyIfNull(username));
        ExtUtil.writeString(out, ExtUtil.emptyIfNull(password));
    }

    @Override
    public void setID(int ID) {
        recordId = ID;
    }

    @Override
    public int getID() {
        return recordId;
    }

    private void checkThatRestoreIsValid(String localRestoreReference)
            throws UnfullfilledRequirementsException, IOException, InvalidStructureException,
            XmlPullParserException, InvalidReferenceException {
        InputStream is = ReferenceManager._().DeriveReference(localRestoreReference).getStream();
        checkThatRestoreIsValid(is);
    }

    private void checkThatRestoreIsValid(InputStream restoreStream)
            throws UnfullfilledRequirementsException, IOException, InvalidStructureException,
            XmlPullParserException {

        TransactionParserFactory factory = new TransactionParserFactory() {
            @Override
            public TransactionParser getParser(KXmlParser parser) {
                String name = parser.getName();
                if ("registration".equals(name.toLowerCase())) {
                    return buildUserParser(parser);
                }
                return null;
            }
        };

        DataModelPullParser parser = new DataModelPullParser(restoreStream, factory, true, false);
        parser.parse();
    }

    private TransactionParser buildUserParser(KXmlParser parser) {
        return new UserXmlParser(parser) {

            @Override
            protected void commit(User parsed) throws IOException, UnfullfilledRequirementsException {
                if (!parsed.getUserType().equals(User.TYPE_DEMO)) {
                    throw new UnfullfilledRequirementsException(
                            "Demo user restore file must be for a user with user_type set to demo",
                            CommCareElementParser.SEVERITY_PROMPT);
                }
                if ("".equals(parsed.getUsername()) || parsed.getUsername() == null) {
                    throw new UnfullfilledRequirementsException(
                            "Demo user restore file must specify a username in the Registration block",
                            CommCareElementParser.SEVERITY_PROMPT);
                } else {
                    OfflineUserRestore.this.username = parsed.getUsername();
                }
            }

            @Override
            public User retrieve(String entityId) {
                return null;
            }

        };
    }
}

@@ -1971,25 +1971,16 @@
 IsValid(
-reference
 );%0A     
@@ -2524,46 +2524,8 @@
 lid(
-new ByteArrayInputStream(restoreBytes)
 );%0A 
@@ -2695,17 +2695,17 @@
 ference 
-=
+!
 = null) 
@@ -2722,94 +2722,212 @@
     
-return getInMemoryStream();%0A        %7D else %7B%0A            return getStreamFromReference
+// user restore xml was installed to a file%0A            return getStreamFromReference();%0A        %7D else %7B%0A            // user restore xml was installed in memory (CLI)%0A            return getInMemoryStream
 ();%0A
@@ -4752,404 +4752,8 @@
 lid(
-String localRestoreReference)%0A            throws UnfullfilledRequirementsException, IOException, InvalidStructureException,%0A            XmlPullParserException, InvalidReferenceException %7B%0A        InputStream is = ReferenceManager._().DeriveReference(localRestoreReference).getStream();%0A        checkThatRestoreIsValid(is);%0A    %7D%0A%0A    private void checkThatRestoreIsValid(InputStream restoreStream
 )%0A  
@@ -5347,17 +5347,20 @@
 lParser(
-r
+getR
 estoreSt
@@ -5363,16 +5363,18 @@
 reStream
+()
 , factor

Consolidate user restore stream access