const Desklet = imports.ui.desklet;
const St = imports.gi.St;
const GLib = imports.gi.GLib;
const Mainloop = imports.mainloop;
const Lang = imports.lang;
const Settings = imports.ui.settings;
const ByteArray = imports.byteArray;

function HardDriveMonitorDesklet(metadata, desklet_id) {
    this._init(metadata, desklet_id);
}

HardDriveMonitorDesklet.prototype = {
    __proto__: Desklet.Desklet.prototype,

    _init: function(metadata, desklet_id) {
        Desklet.Desklet.prototype._init.call(this, metadata, desklet_id);

        this.metadata = metadata;
        this.desklet_id = desklet_id;

        // Default settings
        this.theme = 'dark';
        this.font_size = 13;
        this.title_font_size = 16;
        this.update_interval = 3000;
        this.show_title = true;
        this.card_width = 265;
        this.row_spacing = 10;
        this.device_label = 'device';

        this.settings = new Settings.DeskletSettings(this, this.metadata.uuid, this.desklet_id);
        this.settings.bindProperty(Settings.BindingDirection.IN,
            "theme", "theme", this.on_settings_changed, null);
        this.settings.bindProperty(Settings.BindingDirection.IN,
            "font-size", "font_size", this.on_settings_changed, null);
        this.settings.bindProperty(Settings.BindingDirection.IN,
            "title-font-size", "title_font_size", this.on_settings_changed, null);
        this.settings.bindProperty(Settings.BindingDirection.IN,
            "update-interval", "update_interval", this.on_settings_changed, null);
        this.settings.bindProperty(Settings.BindingDirection.IN,
            "show-title", "show_title", this.on_settings_changed, null);
        this.settings.bindProperty(Settings.BindingDirection.IN,
            "card-width", "card_width", this.on_settings_changed, null);
        this.settings.bindProperty(Settings.BindingDirection.IN,
            "row-spacing", "row_spacing", this.on_settings_changed, null);
        this.settings.bindProperty(Settings.BindingDirection.IN,
            "device-label", "device_label", this.on_settings_changed, null);

        // devices: array of device name strings
        // drive_widgets: array of { deviceLabel, readLabel, writeLabel }
        // prev_stats: object keyed by device name -> { sectors_read, sectors_written }
        this.devices = [];
        this.drive_widgets = [];
        this.prev_stats = {};
        this.timeout = null;

        this.devices = this.detectDevices();
        this.setupUI();
        this.updateStats();
    },

    // Returns a sorted array of real block device names from /proc/diskstats
    detectDevices: function() {
        let found = [];
        try {
            let diskstats = GLib.file_get_contents('/proc/diskstats');
            if (!diskstats[0]) return found;

            let lines = ByteArray.toString(diskstats[1]).split('\n');
            for (let i = 0; i < lines.length; i++) {
                let line = lines[i].trim();
                if (!line) continue;
                let parts = line.split(/\s+/);
                if (parts.length < 14) continue;
                let devname = parts[2];
                if (devname.match(/^sd[a-z]$/)      ||
                    devname.match(/^vd[a-z]$/)      ||
                    devname.match(/^hd[a-z]$/)      ||
                    devname.match(/^nvme\d+n\d+$/)  ||
                    devname.match(/^xvd[a-z]$/)     ||
                    devname.match(/^mmcblk\d+$/)) {
                    found.push(devname);
                }
            }
        } catch (e) {
            global.logError('HDD Monitor: Error detecting devices: ' + e);
        }
        return found;
    },

    getDeviceDisplayName: function(devname) {
        if (this.device_label === 'model') {
            try {
                let data = GLib.file_get_contents('/sys/block/' + devname + '/device/model');
                if (data[0]) {
                    let model = data[1].toString().trim();
                    if (model.length > 0) return model;
                }
            } catch (e) {}
            return devname;
        }

        if (this.device_label === 'label') {
            try {
                let result = GLib.spawn_command_line_sync('lsblk -n -r -o NAME,LABEL');
                if (result[0]) {
                    let lines = ByteArray.toString(result[1]).split('\n');
                    for (let i = 0; i < lines.length; i++) {
                        let parts = lines[i].trim().split(/\s+/);
                        // parts[0] is a partition like sdb1 / nvme0n1p1
                        // check if it belongs to our device and has a label
                        if (parts.length >= 2 && parts[1].length > 0) {
                            let partname = parts[0];
                            // partition belongs to device if name starts with devname
                            if (partname.indexOf(devname) === 0 && partname !== devname) {
                                return parts[1];
                            }
                        }
                    }
                }
            } catch (e) {}
            return devname; // fallback — no label found
        }

        return devname; // default: device name
    },

    setupUI: function() {
        // Card width comes from settings — user-adjustable
        let cardWidth = this.card_width;
        let totalWidth = cardWidth * 2 + 44; // 2 cards + gap + padding

        let containerClass = this.theme === 'light'
            ? 'hdd-monitor-container-light'
            : 'hdd-monitor-container';

        this.window = new St.BoxLayout({
            vertical: true,
            style_class: containerClass
        });
        // Fixed width — does not grow or shrink
        this.window.set_style('width: ' + totalWidth + 'px;');

        if (this.show_title) {
            let titleClass = this.theme === 'light'
                ? 'hdd-monitor-title-light'
                : 'hdd-monitor-title';
            let titleLabel = new St.Label({
                text: 'Drive Monitor',
                style_class: titleClass
            });
            titleLabel.set_style('font-size: ' + this.title_font_size + 'pt; width: ' + (totalWidth - 32) + 'px;');
            this.window.add(titleLabel);
        }

        this.drive_widgets = [];

        if (this.devices.length === 0) {
            let noDevLabel = new St.Label({
                text: 'No drives detected',
                style_class: this.theme === 'light'
                    ? 'hdd-monitor-device-light'
                    : 'hdd-monitor-device'
            });
            this.window.add(noDevLabel);
            this.setContent(this.window);
            return;
        }

        let dividerClass = this.theme === 'light'
            ? 'hdd-monitor-divider-light'
            : 'hdd-monitor-divider';

        // Build drives in pairs — 2 fixed-width columns per row
        for (let i = 0; i < this.devices.length; i += 2) {
            // Divider + spacing between rows
            if (i > 0) {
                let div = new St.BoxLayout({ vertical: false, style_class: dividerClass });
                div.set_style('margin-top: ' + this.row_spacing + 'px; margin-bottom: ' + this.row_spacing + 'px;');
                this.window.add(div);
            }

            let row = new St.BoxLayout({ vertical: false });
            this.window.add(row);

            for (let col = 0; col < 2; col++) {
                let idx = i + col;
                // Fixed width on every card — content never expands it
                let cardStyle = 'width: ' + cardWidth + 'px; overflow: hidden;';
                if (col === 1) cardStyle += ' margin-left: 12px;';

                if (idx >= this.devices.length) {
                    let placeholder = new St.BoxLayout({ vertical: true });
                    placeholder.set_style(cardStyle);
                    row.add(placeholder);
                    this.drive_widgets.push(null);
                    continue;
                }

                let devname = this.devices[idx];
                let card = new St.BoxLayout({ vertical: true });
                card.set_style(cardStyle);
                row.add(card);

                let deviceClass = this.theme === 'light'
                    ? 'hdd-monitor-device-light'
                    : 'hdd-monitor-device';
                let deviceLabel = new St.Label({
                    text: '',
                    style_class: deviceClass
                });
                deviceLabel.set_style('width: ' + (cardWidth - 12) + 'px; font-size: ' + this.font_size + 'pt;');
                deviceLabel.set_text('Device: ' + this.getDeviceDisplayName(devname));
                card.add(deviceLabel);

                let readClass = this.theme === 'light'
                    ? 'hdd-monitor-read-light'
                    : 'hdd-monitor-read';
                let readLabel = new St.Label({
                    text: 'Read:  0.00 KB/s',
                    style_class: readClass
                });
                // Monospace + fixed width keeps the label from ever changing size
                readLabel.set_style('width: ' + (cardWidth - 12) + 'px; font-size: ' + this.font_size + 'pt; font-family: monospace;');
                card.add(readLabel);

                let writeClass = this.theme === 'light'
                    ? 'hdd-monitor-write-light'
                    : 'hdd-monitor-write';
                let writeLabel = new St.Label({
                    text: 'Write: 0.00 KB/s',
                    style_class: writeClass
                });
                writeLabel.set_style('width: ' + (cardWidth - 12) + 'px; font-size: ' + this.font_size + 'pt; font-family: monospace;');
                card.add(writeLabel);

                // Free space bar
                let barBgColor = this.theme === 'light' ? 'rgba(90,110,200,0.18)' : 'rgba(255,255,255,0.10)';
                let barBg = new St.BoxLayout({ vertical: false });
                let barInnerWidth = cardWidth - 12;
                barBg.set_style('width: ' + barInnerWidth + 'px; height: 7px; background-color: ' + barBgColor + '; border-radius: 4px; margin-top: 5px;');

                let barFill = new St.BoxLayout({ vertical: false });
                barFill.set_style('width: 0px; height: 7px; background-color: #57e89c; border-radius: 4px;');
                barBg.add(barFill);
                card.add(barBg);

                let freeLabelColor = this.theme === 'light' ? '#3a5bbf' : '#8ab4f8';
                let freeLabel = new St.Label({ text: '' });
                freeLabel.set_style('font-size: 9pt; color: ' + freeLabelColor + '; margin-top: 2px;');
                card.add(freeLabel);

                this.drive_widgets.push({ deviceLabel: deviceLabel, readLabel: readLabel, writeLabel: writeLabel, barFill: barFill, freeLabel: freeLabel, barWidth: barInnerWidth });
            }
        }

        this.setContent(this.window);
    },

    on_settings_changed: function() {
        if (this.timeout) {
            Mainloop.source_remove(this.timeout);
            this.timeout = null;
        }
        this.prev_stats = {};
        if (this.window) this.window.destroy();

        this.devices = this.detectDevices();
        this.setupUI();
        this.updateStats();
    },

    // Returns map of devname -> { sectors_read, sectors_written }
    getAllDiskStats: function() {
        let result = {};
        try {
            let diskstats = GLib.file_get_contents('/proc/diskstats');
            if (!diskstats[0]) return result;

            let lines = ByteArray.toString(diskstats[1]).split('\n');
            for (let i = 0; i < lines.length; i++) {
                let line = lines[i].trim();
                if (!line) continue;
                let parts = line.split(/\s+/);
                if (parts.length < 14) continue;
                let devname = parts[2];
                result[devname] = {
                    sectors_read: parseInt(parts[5]) || 0,
                    sectors_written: parseInt(parts[9]) || 0
                };
            }
        } catch (e) {
            global.logError('HDD Monitor: Error reading diskstats: ' + e);
        }
        return result;
    },

    getAllDiskFreeInfo: function() {
        let info = {};
        try {
            let result = GLib.spawn_command_line_sync('df --output=source,size,avail -B1');
            if (!result[0]) return info;
            let lines = ByteArray.toString(result[1]).split('\n');
            for (let i = 1; i < lines.length; i++) {
                let line = lines[i].trim();
                if (!line) continue;
                let parts = line.split(/\s+/);
                if (parts.length < 3) continue;
                let source = parts[0];
                if (source.indexOf('/dev/') !== 0) continue;
                let devpart = source.slice(5); // strip /dev/
                let size = parseInt(parts[1]) || 0;
                let avail = parseInt(parts[2]) || 0;
                if (size === 0) continue;
                // map partition (e.g. sda1, nvme0n1p1) back to base device
                for (let d = 0; d < this.devices.length; d++) {
                    let dev = this.devices[d];
                    if (devpart.indexOf(dev) === 0) {
                        if (!info[dev] || size > info[dev].size) {
                            info[dev] = { freePercent: (avail / size) * 100, size: size };
                        }
                    }
                }
            }
        } catch(e) {}
        return info;
    },

    formatSpeed: function(bytesPerSecond) {
        let kb = bytesPerSecond / 1024;
        if (kb < 1024) {
            return kb.toFixed(2) + ' KB/s';
        } else if (kb < 1024 * 1024) {
            return (kb / 1024).toFixed(2) + ' MB/s';
        } else {
            return (kb / 1024 / 1024).toFixed(2) + ' GB/s';
        }
    },

    updateStats: function() {
        let all_stats = this.getAllDiskStats();
        let free_info = this.getAllDiskFreeInfo();
        let interval_sec = this.update_interval / 1000;

        for (let i = 0; i < this.devices.length; i++) {
            let devname = this.devices[i];
            let widgets = this.drive_widgets[i];
            if (!widgets) continue;
            let cur = all_stats[devname];
            if (!cur) continue;

            let prev = this.prev_stats[devname];
            if (prev) {
                let read_bytes  = (cur.sectors_read    - prev.sectors_read)    * 512 / interval_sec;
                let write_bytes = (cur.sectors_written - prev.sectors_written) * 512 / interval_sec;
                if (read_bytes  < 0) read_bytes  = 0;
                if (write_bytes < 0) write_bytes = 0;

                widgets.readLabel.set_text( 'Read:  ' + this.formatSpeed(read_bytes));
                widgets.writeLabel.set_text('Write: ' + this.formatSpeed(write_bytes));
            }

            // Free space bar — data already fetched once for all drives
            if (widgets.barFill && widgets.freeLabel) {
                let fi = free_info[devname];
                if (fi) {
                    let pct = fi.freePercent;
                    let fillW = Math.round((pct / 100) * widgets.barWidth);
                    let color = pct > 50 ? '#57e89c' : pct > 20 ? '#ffcc44' : '#ff5555';
                    widgets.barFill.set_style('width: ' + fillW + 'px; height: 7px; background-color: ' + color + '; border-radius: 4px;');
                    widgets.freeLabel.set_text(pct.toFixed(1) + '% free');
                } else {
                    widgets.freeLabel.set_text('');
                }
            }

            this.prev_stats[devname] = cur;
        }

        this.timeout = Mainloop.timeout_add(this.update_interval, Lang.bind(this, this.updateStats));
    },

    on_desklet_removed: function() {
        if (this.timeout) {
            Mainloop.source_remove(this.timeout);
            this.timeout = null;
        }
    }
};

function main(metadata, desklet_id) {
    return new HardDriveMonitorDesklet(metadata, desklet_id);
}
