/*
 *    HardInfo - Displays System Information
 *    Copyright (C) 2003-2019 L. A. F. Pereira <l@tia.mat.br>
 *    Copyright (C) 2019 Burt P. <pburt0@gmail.com>
 *
 *    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, version 2 or later.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#include "devices.h"
#include <ctype.h>
#include "util_sysobj.h"
#include "util_edid.h"
#include "util_ids.h"

static const char monitor_icon[] = "monitor.svg";

#define UNKIFNULL2(f) ((f) ? f : _("(Unknown)"))
#define UNKIFEMPTY2(f) ((*f) ? f : _("(Unknown)"))
#define UNSPECIFNULL2(f) ((f) ? f : _("(Unspecified)"))

gboolean no_monitors = FALSE;

gchar *find_edid_ids_file() {
    char *file_search_order[] = {
        g_build_filename(g_get_user_config_dir(), "hardinfo2", "edid.ids", NULL),
        g_build_filename(params.path_data, "edid.ids", NULL),
        NULL
    };
    int n=0;
    gchar *ret=NULL;
    while(file_search_order[n]) {
        if (!ret && !access(file_search_order[n], R_OK)) ret=g_strdup(file_search_order[n]);
        g_free(file_search_order[n]);
	n++;
    }
    return ret;
}

gchar *find_ieee_oui_ids_file() {
    char *file_search_order[] = {
        g_build_filename(g_get_user_config_dir(), "hardinfo2", "ieee_oui.ids", NULL),
        g_build_filename(params.path_data, "ieee_oui.ids", NULL),
        NULL
    };
    int n=0;
    gchar *ret=NULL;
    while(file_search_order[n]) {
        if (!ret && !access(file_search_order[n], R_OK)) ret=g_strdup(file_search_order[n]);
	g_free(file_search_order[n]);
	n++;
    }
    return ret;
}

typedef struct {
    gchar *drm_path;
    gchar *drm_connection;
    gchar *drm_status;
    gchar *drm_enabled;
    edid *e;
    gchar *_vstr; /* use monitor_vendor_str() */
} monitor;
#define monitor_new() g_new0(monitor, 1)

monitor *monitor_new_from_sysfs(const gchar *sysfs_edid_file) {
    gchar *edid_bin = NULL;
    gsize edid_len = 0;
    if (!sysfs_edid_file || !*sysfs_edid_file) return NULL;
    monitor *m = monitor_new();
    m->drm_path = g_path_get_dirname(sysfs_edid_file);
    m->drm_connection = g_path_get_basename(m->drm_path);
    gchar *drm_enabled_file = g_strdup_printf("%s/%s", m->drm_path, "enabled");
    gchar *drm_status_file = g_strdup_printf("%s/%s", m->drm_path, "status");
    g_file_get_contents(drm_enabled_file, &m->drm_enabled, NULL, NULL);
    if (m->drm_enabled) g_strstrip(m->drm_enabled);
    g_file_get_contents(drm_status_file, &m->drm_status, NULL, NULL);
    if (m->drm_status) g_strstrip(m->drm_status);
    g_file_get_contents(sysfs_edid_file, &edid_bin, &edid_len, NULL);
    if (edid_len)
        m->e = edid_new(edid_bin, edid_len);
    g_free(drm_enabled_file);
    g_free(drm_status_file);
    return m;
}

void monitor_free(monitor *m) {
    if (m) {
        g_free(m->_vstr);
        g_free(m->drm_connection);
        g_free(m->drm_enabled);
        g_free(m->drm_status);
        g_free(m->drm_path);
        edid_free(m->e);
        g_free(m);
    }
}

gchar *monitor_vendor_str(monitor *m, gboolean include_value, gboolean link_name) {
    if (!m || !m->e) return NULL;
    edid_ven ven = m->e->ven;
    gchar v[20] = "", t[4] = "";
    ids_query_result result;// = {};

    memset(&result,0,sizeof(ids_query_result));
    if (ven.type == VEN_TYPE_PNP) {
        strcpy(v, ven.pnp);
        strcpy(t, "PNP");
    } else if (ven.type == VEN_TYPE_OUI) {
        strcpy(v, ven.oui_str);
        strcpy(t, "OUI");
    }

    if (!m->_vstr) {
        if (ven.type == VEN_TYPE_PNP) {
            gchar *edid_ids_file = find_edid_ids_file();
            scan_ids_file(edid_ids_file, v, &result, -1);
	    g_free(edid_ids_file);
            if (result.results[0])  m->_vstr = g_strdup(result.results[0]);
        } else if (ven.type == VEN_TYPE_OUI) {
            gchar *ieee_oui_ids_file = find_ieee_oui_ids_file();
            scan_ids_file(ieee_oui_ids_file, v, &result, -1);
	    g_free(ieee_oui_ids_file);
            if (result.results[0])
                m->_vstr = g_strdup(result.results[0]);
        }
    }

    gchar *ret = NULL;
    if (include_value)
        ret = g_strdup_printf("[%s:%s]", t, v);
    if (m->_vstr) {
        if (link_name) {
            gchar *lv = vendor_get_link(m->_vstr);
            ret = appfsp(ret, "%s", lv);
            g_free(lv);
        } else
            ret = appfsp(ret, "%s", m->_vstr);
    } else if (!include_value && ven.type == VEN_TYPE_PNP) {
        ret = appfsp(ret, "%s", ven.pnp);
    } else
        ret = appfsp(ret, "%s", _("(Unknown)"));
    return ret;
}

gchar *monitor_name(monitor *m, gboolean include_vendor) {
    if (!m) return NULL;
    gchar *desc = NULL;
    edid *e = m->e;
    if (!e)
        return g_strdup(_("(Unknown)"));

    if (include_vendor) {
        if (e->ven.type != VEN_TYPE_INVALID) {
            gchar *vstr = monitor_vendor_str(m, FALSE, FALSE);
            gchar *vtag = vendor_match_tag(vstr, params.fmt_opts);
                desc = appfsp(desc, "%s", vtag ? vtag : vstr);
            g_free(vstr);
            g_free(vtag);
        } else
            desc = appfsp(desc, "%s", "Unknown");
    }

    if (e->img_max.diag_in)
        desc = appfsp(desc, "%s", e->img_max.class_inch);

    if (e->name)
        desc = appfsp(desc, "%s", e->name);
    else
        desc = appfsp(desc, "%s %s", e->a_or_d ? "Digital" : "Analog", "Display");

    return desc;
}

gchar **get_output_lines(const char *cmd_line) {
    gboolean spawned;
    gchar *out, *err;
    gchar **ret = NULL;

    spawned = g_spawn_command_line_sync(cmd_line,
            &out, &err, NULL, NULL);
    if (spawned) {
        ret = g_strsplit(out, "\n", -1);
        g_free(out);
        g_free(err);
    }
    return ret;
}

static gchar *tag_make_safe_inplace(gchar *tag) {
    if (!tag)
        return tag;
    if (!g_utf8_validate(tag, -1, NULL))
        return tag; //TODO: reconsider
    gchar *p = tag, *pd = tag;
    while(*p) {
        gchar *np = g_utf8_next_char(p);
        gunichar c = g_utf8_get_char_validated(p, -1);
        int l = g_unichar_to_utf8(c, NULL);
        if (l == 1 && g_unichar_isalnum(c)) {
            g_unichar_to_utf8(c, pd);
        } else {
            *pd = '_';
        }
        p = np;
        pd++;
    }
    return tag;
}

static gchar *make_edid_section(monitor *m) {
    int i;
    edid *e = m->e;
    if (e->len) {
        gchar *vstr = monitor_vendor_str(m, TRUE, FALSE);

        gchar *dom = NULL;
        if (!e->dom.is_model_year && e->dom.week && e->dom.year)
            dom = g_strdup_printf(_("Week %d of %d"), e->dom.week, e->dom.year);
        else if (e->dom.year)
            dom = g_strdup_printf("%d", e->dom.year);

        gchar *bpcc = NULL;
        if (e->bpc)
            bpcc = g_strdup_printf("%d", e->bpc);

        int aok = e->checksum_ok;
        if (e->ext_blocks_fail) aok = 0;
        gchar *csum = aok ? _("Ok") : _("Fail");

        gchar *iface;
        if (e->interface && e->di.exists) {
            gchar *tmp = g_strdup_printf("[%x] %s\n[DI-EXT:%x] %s",
                e->interface, _(edid_interface(e->interface)),
                e->di.interface, _(edid_di_interface(e->di.interface)) );
            iface = gg_key_file_parse_string_as_value(tmp, '|');
            g_free(tmp);
        } else if (e->di.exists) {
            iface = g_strdup_printf("[DI-EXT:%x] %s",
                e->di.interface, _(edid_di_interface(e->di.interface)) );
        } else {
            iface = g_strdup_printf("[%x] %s",
                e->interface,
                e->interface ? _(edid_interface(e->interface)) : _("(Unspecified)") );
        }

        gchar *d_list, *ext_list, *dtd_list, *cea_list,
            *etb_list, *std_list, *svd_list, *sad_list,
            *didt_list, *did_string_list;

        etb_list = NULL;
        for(i = 0; i < e->etb_count; i++) {
            char *desc = edid_output_describe(&e->etbs[i]);
            etb_list = appfnl(etb_list, "etb%d=%s", i, desc);
            g_free(desc);
        }
        if (!etb_list) etb_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        std_list = NULL;
        for(i = 0; i < e->std_count; i++) {
            char *desc = edid_output_describe(&e->stds[i].out);
            std_list = appfnl(std_list, "std%d=%s", i, desc);
            g_free(desc);
        }
        if (!std_list) std_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        d_list = NULL;
        for(i = 0; i < 4; i++) {
            char *desc = edid_base_descriptor_describe(&e->d[i]);
            d_list = appfnl(d_list, "descriptor%d=%s", i, desc);
            g_free(desc);
        }
        if (!d_list) d_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        ext_list = NULL;
        for(i = 0; i < e->ext_blocks; i++) {
            int type = e->u8[(i+1)*128];
            int version = e->u8[(i+1)*128 + 1];
            ext_list = appfnl(ext_list, "ext%d = ([%02x:v%02x] %s) %s", i,
                type, version, _(edid_ext_block_type(type)),
                e->ext_ok[i] ? "ok" : "fail"
            );
        }
        if (!ext_list) ext_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        dtd_list = NULL;
        for(i = 0; i < e->dtd_count; i++) {
            char *desc = edid_dtd_describe(&e->dtds[i], 0);
            dtd_list = appfnl(dtd_list, "dtd%d = %s", i, desc);
            free(desc);
        }
        if (!dtd_list) dtd_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        cea_list = NULL;
        for(i = 0; i < e->cea_block_count; i++) {
            char *desc = edid_cea_block_describe(&e->cea_blocks[i]);
            cea_list = appfnl(cea_list, "cea_block%d = %s", i, desc);
        }
        if (!cea_list) cea_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        svd_list = NULL;
        for(i = 0; i < e->svd_count; i++) {
            char *desc = edid_output_describe(&e->svds[i].out);
            svd_list = appfnl(svd_list, "svd%d=%s", i, desc);
            g_free(desc);
        }
        if (!svd_list) svd_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        sad_list = NULL;
        for(i = 0; i < e->sad_count; i++) {
            char *desc = edid_cea_audio_describe(&e->sads[i]);
            sad_list = appfnl(sad_list, "sad%d=%s", i, desc);
            g_free(desc);
        }
        if (!sad_list) sad_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        didt_list = NULL;
        for(i = 0; i < e->didt_count; i++) {
            char *desc = edid_output_describe(&e->didts[i]);
            didt_list = appfnl(didt_list, "didt%d=%s", i, desc);
            g_free(desc);
        }
        if (!didt_list) didt_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        did_string_list = NULL;
        for(i = 0; i < e->did_string_count; i++) {
            did_string_list = appfnl(did_string_list, "did_string%d=%s", i, e->did_strings[i].str);
        }
        if (!did_string_list) did_string_list = g_strdup_printf("%s=\n", _("(Empty List)"));

        gchar *speakers = NULL;
        if (e->speaker_alloc_bits) {
            gchar *spk_tmp = edid_cea_speaker_allocation_describe(e->speaker_alloc_bits, 0);
            speakers = gg_key_file_parse_string_as_value(spk_tmp, '|');
            g_free(spk_tmp);
        } else
            speakers = g_strdup(_("(Unspecified)"));

        gchar *hex = edid_dump_hex(e, 0, 1);
        gchar *hex_esc = gg_key_file_parse_string_as_value(hex, '|');
        g_free(hex);
        if (params.markup_ok)
            hex = g_strdup_printf("<tt>%s</tt>", hex_esc);
        else
            hex = g_strdup(hex_esc);
        g_free(hex_esc);

        gchar *ret = g_strdup_printf(
            /* extending "Connection" section */
            "%s=%s\n" /* sig type */
            "%s=%s\n" /* interface */
            "%s=%s\n" /* bpcc */
            "%s=%s\n" /* speakers */
            "[%s]\n"
            "%s=%s\n" /* base out */
            "%s=%s\n" /* ext out */
            "[%s]\n"
            "$^$%s=%s\n" /* vendor */
            "%s=%s\n" /* name */
            "%s=[%04x-%08x] %u-%u\n" /* model, n_serial */
            "%s=%s\n" /* serial */
            "%s=%s\n" /* dom */
            "[%s]\n"
            "%s=%d %s\n" /* size */
            "%s=%d.%d\n" /* version */
            "%s=%d\n" /* ext block */
            "%s=%s\n" /* ext to */
            "%s=%s %s\n" /* checksum */
            "[%s]\n%s\n"
            "[%s]\n%s\n"
            "[%s]\n%s\n"
            "[%s]\n%s\n"
            "[%s]\n%s\n"
            "[%s]\n%s\n"
            "[%s]\n%s\n"
            "[%s]\n%s\n"
            "[%s]\n%s\n"
            "[%s]\n%s\n"
            "[%s]\n%s=%s\n"
            ,
            _("Signal Type"), e->a_or_d ? _("Digital") : _("Analog"),
            _("Interface"), iface,
            _("Bits per Color Channel"), UNSPECIFNULL2(bpcc),
            _("Speaker Allocation"), speakers,
            _("Output (Max)"),
            edid_output_src(e->img.src), edid_output_describe(&e->img),
            edid_output_src(e->img_max.src), edid_output_describe(&e->img_max),
            _("EDID Device"),
            _("Vendor"), vstr,
            _("Name"), e->name,
            _("Model"), e->product, e->n_serial, e->product, e->n_serial,
            _("Serial"), UNKIFNULL2(e->serial),
            _("Manufacture Date"), UNKIFNULL2(dom),
            _("EDID Meta"),
            _("Data Size"), e->len, _("bytes"),
            _("Version"), (int)e->ver_major, (int)e->ver_minor,
            _("Extension Blocks"), e->ext_blocks,
            _("Extended to"), e->std ? _(edid_standard(e->std)) : _("(None)"),
            _("Checksum"), csum, aok ? "" : problem_marker(),
            _("EDID Descriptors"), d_list,
            _("Detailed Timing Descriptors (DTD)"), dtd_list,
            _("Established Timings Bitmap (ETB)"), etb_list,
            _("Standard Timings (STD)"), std_list,
            _("E-EDID Extension Blocks"), ext_list,
            _("EIA/CEA-861 Data Blocks"), cea_list,
            _("EIA/CEA-861 Short Audio Descriptors"), sad_list,
            _("EIA/CEA-861 Short Video Descriptors"), svd_list,
            _("DisplayID Timings"), didt_list,
            _("DisplayID Strings"), did_string_list,
            _("Hex Dump"), _("Data"), hex
            );
        g_free(bpcc);
        g_free(dom);

        g_free(d_list);
        g_free(ext_list);
        g_free(etb_list);
        g_free(std_list);
        g_free(dtd_list);
        g_free(cea_list);
        g_free(sad_list);
        g_free(svd_list);
        g_free(didt_list);
        g_free(did_string_list);
        g_free(iface);
        g_free(vstr);
        g_free(hex);
        //printf("ret: %s\n", ret);
        return ret;
    } else
        return g_strdup("");
}

gchar *monitors_get_info() {
    gchar *icons = g_strdup("");
    gchar *ret = g_strdup_printf("[%s]\n", _("Monitors"));
    gchar tag_prefix[] = "DEV";

    gchar **edid_files = get_output_lines("find /sys/devices -name edid");
    //gchar **edid_files = get_output_lines("find /home/pburt/github/verbose-spork/junk/testing/.testing/edid2/ -name edid.*");
    int i, found = 0;
    for(i = 0; edid_files[i]; i++) {
        monitor *m = monitor_new_from_sysfs(edid_files[i]);
        //if (m && m->e->std < STD_DISPLAYID) continue;
        //if (m && !m->e->interface) continue;
        //if (m && m->e->interface != 1) continue;
        if (m && !SEQ(m->drm_status, "disconnected")) {
            gchar *tag = g_strdup_printf("%d-%s", found, m->drm_connection);
            tag_make_safe_inplace(tag);
            gchar *desc = monitor_name(m, TRUE);
            gchar *edid_section = NULL;
            edid *e = m->e;
            if (e && e->checksum_ok)
                 edid_section = make_edid_section(m);

            gchar *details = g_strdup_printf("[%s]\n"
                                "%s=%s\n"
                                "%s=%s %s\n"
                                "%s\n",
                                _("Connection"),
                                _("DRM"), m->drm_connection,
                                _("Status"), m->drm_status, m->drm_enabled,
                                edid_section ? edid_section : ""
                                );
            moreinfo_add_with_prefix(tag_prefix, tag, details); /* moreinfo now owns *details */
            ret = h_strdup_cprintf("$!%s$%s=%s\n",
                                    ret, tag, m->drm_connection, desc);
            icons = h_strdup_cprintf("Icon$%s$=%s\n", icons, tag, monitor_icon);
            g_free(desc);
            g_free(edid_section);
            found++;
        }
        monitor_free(m);
    }
    g_strfreev(edid_files);

    no_monitors = FALSE;
    if(!found) {
        no_monitors = TRUE;
        g_free(ret);
        ret = g_strdup_printf("[%s]\n%s=%s\n" "[$ShellParam$]\nViewType=0\n",
                _("Monitors"), _("Result"), _("(Empty)") );
    } else {
        ret = h_strdup_cprintf(
            "[$ShellParam$]\nViewType=1\n"
            "ColumnTitle$TextValue=%s\n" /* DRM connection */
            "ColumnTitle$Value=%s\n"     /* Name */
            "ShowColumnHeaders=true\n"
            "%s",
            ret,
            _("Connection"),
            _("Name"),
            icons
            );
    }

    return ret;
}

gboolean monitors_hinote(const char **msg) {
    PARAM_NOT_UNUSED(msg);
    return FALSE;
}
