/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * Copyright (C) 2014 Michael Catanzaro
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
 * license.
 */

[DBus (name = "org.gnome.Shell.SearchProvider2")]
public class SearchProvider : Object
{
    private static string terms_to_equation (string[] terms)
    {
        return string.joinv (" ", terms);
    }

    private static bool can_parse (string[] terms)
    {
        try
        {
            int exit_status;

            var tsep_string = Posix.nl_langinfo (Posix.NLItem.THOUSEP);
            if (tsep_string == null || tsep_string == "")
                tsep_string = " ";

            var decimal = Posix.nl_langinfo (Posix.NLItem.RADIXCHAR);
            if (decimal == null)
                decimal = "";

            // "normalize" input to a format known to double.try_parse
            var equation = terms_to_equation (terms).replace (tsep_string, "").replace (decimal, ".");

            // if the search is a plain number, don't process it
            if (double.try_parse (equation)) {
                return false;
            }

            // Eat output so that it doesn't wind up in the journal. It's
            // expected that most searches are not valid calculator input.
            string stdout_buf;
            string stderr_buf;
            Process.spawn_command_line_sync (
                "gnome-calculator --solve " + Shell.quote (equation),
                out stdout_buf, out stderr_buf, out exit_status);
            Process.check_exit_status (exit_status);
        }
        catch (SpawnError e)
        {
            error ("Failed to spawn Calculator: %s", e.message);
        }
        catch (Error e)
        {
            return false;
        }

        return true;
    }

    private static string[] get_result_identifier (string[] terms)
        ensures (result.length == 0 || result.length == 1)
    {
        /* We have at most one result: the search terms as one string */
        if (can_parse (terms))
            return { terms_to_equation (terms) };
        else
            return new string[0];
    }

    public string[] get_initial_result_set (string[] terms)
    {
        return get_result_identifier (terms);
    }

    public string[] get_subsearch_result_set (string[] previous_results, string[] terms)
    {
        return get_result_identifier (terms);
    }

    public HashTable<string, Variant>[] get_result_metas (string[] results)
        requires (results.length == 1)
        ensures (result.length == 1)
    {
        Subprocess subprocess;
        string stdout_buf;
        string stderr_buf;

        string[] argv = {"gnome-calculator", "--solve"};
        argv += results[0];
        argv += null;

        try
        {
            subprocess = new Subprocess.newv (argv, SubprocessFlags.STDOUT_PIPE | SubprocessFlags.STDERR_PIPE);
        }
        catch (Error e)
        {
            error ("Failed to spawn Calculator: %s", e.message);
        }

        try
        {
            subprocess.communicate_utf8 (null, null, out stdout_buf, out stderr_buf);
        }
        catch (Error e)
        {
            error ("Failed reading result: %s", e.message);
        }

        var metadata = new HashTable<string, Variant>[1];
        metadata[0] = new HashTable<string, Variant>(str_hash, str_equal);
        metadata[0].insert ("id", results[0]);
        metadata[0].insert ("name", results[0] );
        metadata[0].insert ("description", " = " + stdout_buf.strip() );

        return metadata;
    }

    private static void spawn_and_display_equation (string[] terms)
    {
        try
        {
            Process.spawn_command_line_async (
                "gnome-calculator --equation " + Shell.quote (terms_to_equation (terms)));
        }
        catch (SpawnError e)
        {
            error ("Failed to spawn Calculator: %s", e.message);
        }
    }

    public void activate_result (string result, string[] terms, uint32 timestamp)
    {
        spawn_and_display_equation (terms);
    }

    public void launch_search (string[] terms, uint32 timestamp)
    {
        spawn_and_display_equation (terms);
    }
}

/* Based on GPLv2+ code from GNOME Contacts */
public class SearchProviderApp : Application
{
    public SearchProviderApp ()
    {
        Object (application_id: "org.gnome.Calculator.SearchProvider",
                flags: ApplicationFlags.IS_SERVICE,
                inactivity_timeout: 60000);
    }

    public override bool dbus_register (DBusConnection connection, string object_path)
    {
        try
        {
            connection.register_object (object_path, new SearchProvider ());
        }
        catch (IOError error)
        {
            stderr.printf ("Could not register service: %s\n", error.message);
            quit ();
        }

        return true;
    }
}

int main ()
{
    Intl.setlocale (LocaleCategory.ALL, "");

    return new SearchProviderApp ().run ();
}
