/*
 * Copyright (c) 2011-2016, JANET(UK)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of JANET(UK) nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
*/

using Gee;

[DBus (name = "org.janet.Moonshot")]
public class MoonshotServer : Object {

    static MoonshotLogger logger = get_logger("MoonshotServer");

    private string app_name = "Moonshot";

    private IdentityManagerApp parent_app;

    public MoonshotServer(IdentityManagerApp app)
    {
        logger.trace("MoonshotServer.<constructor>; app=" + (app == null ? "null" : "non-null"));
        this.parent_app = app;
    }

    /**
     * DBus method "ShowUi"
     *
     * The return data signature is the declared "out" parameters plus the return
     * value of this method.
     *
     * In this case, that is (b).
     */
    public bool show_ui()
    {
        logger.trace("MoonshotServer.show_ui");

        if (parent_app.view == null) {
            stderr.printf(app_name, "show_ui: parent_app.view is null!\n");
            logger.warn("show_ui: parent_app.view is null!");
            return false;
        }
        parent_app.show();
        parent_app.explicitly_launched = true;
        logger.trace("MoonshotServer.show_ui: returning true");
        return true;
    }

    /**
     * DBus method "GetIdentity"
     *
     * The return data signature is the declared "out" parameters plus the return
     * value of this method.
     *
     * In this case, that is (ssssssb).
     */
    public async bool get_identity(string nai,
                                   string password,
                                   string service,
                                   out string nai_out,
                                   out string password_out,
                                   out string server_certificate_hash,
                                   out string ca_certificate,
                                   out string subject_name_constraint,
                                   out string subject_alt_name_constraint)
    {
        logger.trace(@"MoonshotServer.get_identity: nai='$nai'; service='$service'");
        var request = new IdentityRequest(parent_app,
                                          nai,
                                          password,
                                          service);
        logger.trace(@"MoonshotServer.get_identity: Calling request.execute()");
        request.set_callback((IdentityRequest) => get_identity.callback());
        request.execute();
        logger.trace(@"MoonshotServer.get_identity: Back from request.execute()");
        yield;
        logger.trace(@"MoonshotServer.get_identity: back from yield");

        nai_out = "";
        password_out = "";
        server_certificate_hash = "";
        ca_certificate = "";
        subject_name_constraint = "";
        subject_alt_name_constraint = "";

        var id_card = request.id_card;

        if ((id_card != null) && (!id_card.is_no_identity())) {
            nai_out = id_card.nai;
            if ((request.password != null) && (request.password != ""))
                password_out = request.password;
            else
                password_out = id_card.password;

            server_certificate_hash = id_card.trust_anchor.server_cert;
            ca_certificate = id_card.trust_anchor.ca_cert;
            subject_name_constraint = id_card.trust_anchor.subject;
            subject_alt_name_constraint = id_card.trust_anchor.subject_alt;

            if (nai_out == null)
                nai_out = "";
            if (password_out == null)
                password_out = "";
            if (server_certificate_hash == null)
                server_certificate_hash = "";
            if (ca_certificate == null)
                ca_certificate = "";
            if (subject_name_constraint == null)
                subject_name_constraint = "";
            if (subject_alt_name_constraint == null)
                subject_alt_name_constraint = "";

            logger.trace(@"MoonshotServer.get_identity: returning with nai_out=$nai_out");

            return true;
        }

        logger.trace("MoonshotServer.get_identity: returning false");
        return false;
    }

    /**
     * DBus method "GetDefaultIdentity"
     *
     * The return data signature is the declared "out" parameters plus the return
     * value of this method.
     *
     * In this case, that is (ssssssb).
     */
    public async bool get_default_identity(out string nai_out,
                                           out string password_out,
                                           out string server_certificate_hash,
                                           out string ca_certificate,
                                           out string subject_name_constraint,
                                           out string subject_alt_name_constraint)
    {
        logger.trace("MoonshotServer.get_default_identity");
        var request = new IdentityRequest.default(parent_app);
        request.set_callback((IdentityRequest) => get_default_identity.callback());
        request.execute();
        yield;

        nai_out = "";
        password_out = "";
        server_certificate_hash = "";
        ca_certificate = "";
        subject_name_constraint = "";
        subject_alt_name_constraint = "";

        if (request.id_card != null)
        {
            nai_out = request.id_card.nai;
            password_out = request.id_card.password;

            server_certificate_hash = request.id_card.trust_anchor.server_cert;
            ca_certificate = request.id_card.trust_anchor.ca_cert;
            subject_name_constraint = request.id_card.trust_anchor.subject;
            subject_alt_name_constraint = request.id_card.trust_anchor.subject_alt;

            if (nai_out == null)
                nai_out = "";
            if (password_out == null)
                password_out = "";
            if (server_certificate_hash == null)
                server_certificate_hash = "";
            if (ca_certificate == null)
                ca_certificate = "";
            if (subject_name_constraint == null)
                subject_name_constraint = "";
            if (subject_alt_name_constraint == null)
                subject_alt_name_constraint = "";

            logger.trace("MoonshotServer.get_default_identity: returning true");
            return true;
        }

        return false;
    }

    /**
     * DBus method "InstallIdCard"
     *
     * The return data signature is the declared "out" parameters plus the return
     * value of this method.
     *
     * In this case, that is (b).
     */
    public bool install_id_card(string   display_name,
                                string   user_name,
                                string   ?password,
                                string   ?realm,
                                string[] ?rules_patterns,
                                string[] ?rules_always_confirm,
                                string[] ?services,
                                string   ?ca_cert,
                                string   ?subject,
                                string   ?subject_alt,
                                string   ?server_cert,
                                int      force_flat_file_store)
    {
        IdCard idcard = new IdCard();

        idcard.display_name = display_name;
        idcard.username = user_name;
        idcard.password = password;
        if ((password != null) && (password != ""))
            idcard.store_password = true;
        idcard.issuer = realm;
        idcard.update_services(services);
        var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt);

        if (!ta.is_empty()) {
            // We have to set the datetime_added here, because it isn't delivered via IPC.
            string ta_datetime_added = TrustAnchor.format_datetime_now();
            ta.set_datetime_added(ta_datetime_added);
            logger.trace("install_id_card : Set ta_datetime_added for '%s' to '%s'; ca_cert='%s'; server_cert='%s'".printf(idcard.display_name, ta.datetime_added, ta.ca_cert, ta.server_cert));
        }
        idcard.set_trust_anchor_from_store(ta);

        logger.trace("install_id_card: Card '%s' has services: '%s'"
                     .printf(idcard.display_name, idcard.get_services_string("; ")));

        logger.trace(@"Installing IdCard named '$(idcard.display_name)'; ca_cert='$(idcard.trust_anchor.ca_cert)'; server_cert='$(idcard.trust_anchor.server_cert)'");


        if (rules_patterns.length == rules_always_confirm.length)
        {
            /* workaround Centos vala array property bug: use temp array */
            Rule[] rules = new Rule[rules_patterns.length];
         
            for (int i = 0; i < rules.length; i++)
            { 
                rules[i].pattern = rules_patterns[i];
                rules[i].always_confirm = rules_always_confirm[i];
            }
            idcard.rules = rules;
        }

        ArrayList<IdCard>? old_duplicates = null;
        var ret = parent_app.add_identity(idcard, (force_flat_file_store != 0), out old_duplicates);

        if (old_duplicates != null) {
            // Printing to stdout here is ugly behavior; but it's old behavior that
            // may be expected. (TODO: Do we need to keep this?)
            foreach (IdCard id_card in old_duplicates) {
                stdout.printf("removed duplicate id for '%s'\n", id_card.nai);
            }
        }
        return ret;
    }


    /**
     * DBus method "InstallFromFile"
     *
     * The return data signature is the declared "out" parameters plus the return
     * value of this method.
     *
     * In this case, that is (b).
     */
    public int install_from_file(string file_name)
    {
        var webp = new WebProvisioning.Parser(file_name);

        webp.parse();
        bool result = false;
        int installed_cards = 0;
        foreach (IdCard card in webp.cards)
        {
            string[] rules_patterns = {};
            string[] rules_always_confirm = {};
        
            if (card.rules.length > 0)
            {
                int i = 0;
                rules_patterns = new string[card.rules.length];
                rules_always_confirm = new string[card.rules.length];
                foreach (Rule r in card.rules)
                {
                    rules_patterns[i] = r.pattern;
                    rules_always_confirm[i] = r.always_confirm;
                    i++;
                }
            } 


            // prevent a crash by holding the reference to otherwise
            // unowned array(?)

            // string[] svcs = card.services.to_array();
            // string[] svcs = card.services.to_array()[:];
            string[] svcs = new string[card.services.size];
            for (int i = 0; i < card.services.size; i++) {
                svcs[i] = card.services[i];
            }

            logger.trace(@"install_from_file: Adding card with display name '$(card.display_name)'");
            result = install_id_card(card.display_name,
                                     card.username,
                                     card.password,
                                     card.issuer,
                                     rules_patterns,
                                     rules_always_confirm,
                                     svcs,
                                     card.trust_anchor.ca_cert,
                                     card.trust_anchor.subject,
                                     card.trust_anchor.subject_alt,
                                     card.trust_anchor.server_cert,
                                     0);
            if (result) {
                installed_cards++;
            }
        }
        return installed_cards;
    }

    /**
     * DBus method "ConfirmCaCertificate"
     *
     * The return data signature is the declared "out" parameters plus the return
     * value of this method.
     *
     * In this case, that is (ib).
     */
    public async bool confirm_ca_certificate(string nai,
                                             string realm,
                                             string ca_hash,
                                             out int confirmed)
    {
        logger.trace(@"MoonshotServer.confirm_ca_certificate: nai='$nai'; realm='$realm'; ca_hash='$ca_hash'");

        var request = new TrustAnchorConfirmationRequest(parent_app, nai, realm, ca_hash);
        request.set_callback((TrustAnchorConfirmationRequest) => confirm_ca_certificate.callback());
        request.execute();
        yield;

        confirmed = (request.confirmed ? 1 : 0);
        logger.trace(@"MoonshotServer.confirm_ca_certificate: confirmed=$confirmed");
        return true;
    }
}
