#include "spk.h"
#include "filesystem.h"
#include "string.h"
#include "stdlib.h"
#include "user.h"

extern void print(const char *s);
extern void print_num(int n);
extern void putchar(char c);

struct SpkHeader {
    char magic[4];
    unsigned int version;
    char name[SPK_NAME_LEN];
    char pkg_version[SPK_VER_LEN];
    char desc[SPK_DESC_LEN];
    unsigned int file_count;
} __attribute__((packed));

struct SpkFileHeader {
    char path[SPK_PATH_LEN];
    unsigned int size;
} __attribute__((packed));

static int ensure_dir(int parent, const char *name)
{
    int idx = fs_find_in(parent, name);
    if (idx >= 0 && fs_is_dir(idx)) return idx;
    return fs_mkdir(parent, name);
}

static int spk_root_dir(void)
{
    int root = fs_cd(0, "/");
    int var, lib, spk;
    if (root < 0) return -1;
    var = ensure_dir(root, "var");
    if (var < 0) return -1;
    lib = ensure_dir(var, "lib");
    if (lib < 0) return -1;
    spk = ensure_dir(lib, "spk");
    return spk;
}

int spk_init(void)
{
    int d = spk_root_dir();
    if (d < 0) { print("spk: init failed\n"); return -1; }
    return 0;
}

static int resolve_path_create(const char *path, int *out_parent,
                               char *out_name, int name_max)
{
    int cur = 0;
    const char *p = path;
    char part[MAX_NAME];
    char last_part[MAX_NAME];
    int i;

    last_part[0] = '\0';
    if (*p == '/') p++;

    while (*p) {
        i = 0;
        while (*p && *p != '/' && i < MAX_NAME - 1) part[i++] = *p++;
        part[i] = '\0';
        if (*p == '/') p++;
        if (i == 0) continue;

        if (*p == '\0') {
            strncpy(out_name, part, name_max);
            out_name[name_max - 1] = '\0';
            *out_parent = cur;
            return 0;
        }

        int idx = fs_find_in(cur, part);
        if (idx < 0) {
            idx = fs_mkdir(cur, part);
            if (idx < 0) return -1;
        } else if (!fs_is_dir(idx)) {
            return -1;
        }
        cur = idx;
    }
    return -1;
}

static int resolve_path(const char *path, int *out_parent,
                        char *out_name, int name_max)
{
    char tmp[256];
    char *p;
    int parent;

    if (strlen(path) >= sizeof(tmp)) return -1;
    strcpy(tmp, path);

    p = tmp;
    while (*p) p++;
    while (p > tmp && p[-1] != '/') p--;

    if (p == tmp) {
        parent = 0;
        strncpy(out_name, tmp, name_max);
        out_name[name_max - 1] = '\0';
    } else {
        p[-1] = '\0';
        parent = fs_cd(0, tmp);
        strncpy(out_name, p, name_max);
        out_name[name_max - 1] = '\0';
    }
    if (parent < 0) return -1;
    *out_parent = parent;
    return 0;
}


int spk_build(const char *outfile, const char *name, const char *version,
              const char *desc, const char **files, int nfiles)
{
    unsigned char *buf;
    struct SpkHeader *hdr;
    int total_size = sizeof(struct SpkHeader);
    int pos, i, j;

    if (nfiles > SPK_MAX_FILES) {
        print("spk: too many files (max "); print_num(SPK_MAX_FILES); print(")\n");
        return -1;
    }

    /* 先估算总大小 */
    for (i = 0; i < nfiles; i++) {
        int parent;
        char fname[MAX_NAME];
        int idx, sz;
        if (resolve_path(files[i], &parent, fname, MAX_NAME) < 0) {
            print("spk: cannot resolve "); print(files[i]); putchar('\n');
            return -1;
        }
        idx = fs_find_in(parent, fname);
        if (idx < 0) {
            print("spk: file not found: "); print(files[i]); putchar('\n');
            return -1;
        }
        sz = fs_size_of(idx);
        total_size += sizeof(struct SpkFileHeader) + sz;
    }

    if (total_size > 64000) {
        print("spk: package too large\n");
        return -1;
    }

    buf = (unsigned char *)malloc(total_size);
    if (!buf) { print("spk: out of memory\n"); return -1; }
    memset(buf, 0, total_size);

    hdr = (struct SpkHeader *)buf;
    hdr->magic[0] = 'S'; hdr->magic[1] = 'P';
    hdr->magic[2] = 'K'; hdr->magic[3] = '1';
    hdr->version = 1;
    strncpy(hdr->name, name, SPK_NAME_LEN - 1);
    strncpy(hdr->pkg_version, version, SPK_VER_LEN - 1);
    strncpy(hdr->desc, desc, SPK_DESC_LEN - 1);
    hdr->file_count = (unsigned int)nfiles;

    pos = sizeof(struct SpkHeader);

    for (i = 0; i < nfiles; i++) {
        struct SpkFileHeader *fh;
        int parent, sz;
        char fname[MAX_NAME];

        if (resolve_path(files[i], &parent, fname, MAX_NAME) < 0) {
            free(buf);
            return -1;
        }
        sz = fs_read_at(parent, fname, buf + pos + sizeof(struct SpkFileHeader), 32768);
        if (sz < 0) sz = 0;

        fh = (struct SpkFileHeader *)(buf + pos);
        strncpy(fh->path, files[i], SPK_PATH_LEN - 1);
        fh->size = (unsigned int)sz;

        pos += sizeof(struct SpkFileHeader) + sz;
    }

    {
        int oparent;
        char oname[MAX_NAME];
        if (resolve_path_create(outfile, &oparent, oname, MAX_NAME) < 0) {
            free(buf);
            print("spk: bad output path\n");
            return -1;
        }
        if (fs_find_in(oparent, oname) < 0)
            fs_create_ex(oparent, oname, FS_FILE, current_uid, 0644);
        fs_write_at(oparent, oname, buf, pos);
    }

    free(buf);
    print("Built "); print(outfile);
    print(" ("); print_num(pos); print(" bytes, ");
    print_num(nfiles); print(" files)\n");
    return 0;
}


int spk_install(const char *pkgfile)
{
    unsigned char *buf;
    struct SpkHeader *hdr;
    unsigned int pos, i;
    int sz, err = 0;
    int spkdir;

    buf = (unsigned char *)malloc(65536);
    if (!buf) { print("spk: out of memory\n"); return -1; }

    sz = fs_read(pkgfile, buf, 65536);
    if (sz <= 0) {
        free(buf);
        print("spk: cannot read "); print(pkgfile); putchar('\n');
        return -1;
    }
    if (sz < (int)sizeof(struct SpkHeader)) {
        free(buf);
        print("spk: file too small\n");
        return -1;
    }

    hdr = (struct SpkHeader *)buf;
    if (hdr->magic[0] != 'S' || hdr->magic[1] != 'P' ||
        hdr->magic[2] != 'K' || hdr->magic[3] != '1') {
        free(buf);
        print("spk: not a valid spk package\n");
        return -1;
    }
    if (hdr->file_count > SPK_MAX_FILES) {
        free(buf);
        print("spk: too many files in package\n");
        return -1;
    }

    spkdir = spk_root_dir();
    if (spkdir < 0) { free(buf); return -1; }

    {
        char *db = (char *)malloc(16384);
        int dbsize = fs_find_in(spkdir, "installed") >= 0
                     ? fs_read_at(spkdir, "installed", db, 16384) : 0;
        int p = 0;
        if (dbsize > 0) {
            db[dbsize] = '\0';
            while (p < dbsize) {
                char line[128];
                int len = 0;
                while (p < dbsize && db[p] != '\n' && len < 127) line[len++] = db[p++];
                line[len] = '\0';
                if (p < dbsize) p++;
                if (len == 0) continue;
                {
                    char nm[64]; int k = 0;
                    char *lp = line;
                    while (*lp && *lp != ' ' && k < 63) nm[k++] = *lp++;
                    nm[k] = '\0';
                    if (strcmp(nm, hdr->name) == 0) {
                        print("spk: "); print(hdr->name);
                        print(" is already installed\n");
                        free(db); free(buf);
                        return -1;
                    }
                }
                {
                    char *lp = line;
                    int nf = 0;
                    while (*lp && *lp != ' ') lp++;
                    if (*lp == ' ') lp++;
                    while (*lp && *lp != ' ') lp++;
                    if (*lp == ' ') lp++;
                    while (*lp >= '0' && *lp <= '9') { nf = nf * 10 + (*lp - '0'); lp++; }
                    while (nf-- > 0) {
                        while (p < dbsize && db[p] != '\n') p++;
                        if (p < dbsize) p++;
                    }
                }
            }
        }
        free(db);
    }

    print("Installing "); print(hdr->name);
    print(" "); print(hdr->pkg_version); putchar('\n');

    pos = sizeof(struct SpkHeader);
    for (i = 0; i < hdr->file_count; i++) {
        struct SpkFileHeader *fh;
        int parent, idx;
        char fname[MAX_NAME];

        if (pos + sizeof(struct SpkFileHeader) > (unsigned int)sz) { err = 1; break; }
        fh = (struct SpkFileHeader *)(buf + pos);
        pos += sizeof(struct SpkFileHeader);

        if (pos + fh->size > (unsigned int)sz) { err = 1; break; }

        if (resolve_path_create(fh->path, &parent, fname, MAX_NAME) < 0) {
            print("spk: bad path "); print(fh->path); putchar('\n');
            err = 1; break;
        }

        idx = fs_find_in(parent, fname);
        if (idx < 0) {
            fs_create_ex(parent, fname, FS_FILE, 0, 0644);
        }
        fs_write_at(parent, fname, buf + pos, fh->size);

        print("  "); print(fh->path);
        print(" ("); print_num(fh->size); print(" bytes)\n");

        pos += fh->size;
    }

    if (!err) {
        char *db = (char *)malloc(16384);
        int dbsize = 0, outpos = 0;
        int dbi;

        if (db) {
            dbi = fs_find_in(spkdir, "installed");
            if (dbi >= 0)
                dbsize = fs_read_at(spkdir, "installed", db, 16000);
            if (dbsize < 0) dbsize = 0;
            outpos = dbsize;

            {
                const char *sp = hdr->name;
                while (*sp && outpos < 16380) db[outpos++] = *sp++;
                if (outpos < 16380) db[outpos++] = ' ';
                sp = hdr->pkg_version;
                while (*sp && outpos < 16380) db[outpos++] = *sp++;
                if (outpos < 16380) db[outpos++] = ' ';
                {
                    char t[12]; int ti = 0, n = (int)hdr->file_count;
                    if (n == 0) t[ti++] = '0';
                    while (n > 0) { t[ti++] = '0' + n % 10; n /= 10; }
                    while (ti > 0 && outpos < 16380) db[outpos++] = t[--ti];
                }
                if (outpos < 16380) db[outpos++] = '\n';
            }
            {
                unsigned int pp = sizeof(struct SpkHeader);
                unsigned int k;
                for (k = 0; k < hdr->file_count; k++) {
                    struct SpkFileHeader *fh = (struct SpkFileHeader *)(buf + pp);
                    const char *sp = fh->path;
                    while (*sp && outpos < 16380) db[outpos++] = *sp++;
                    if (outpos < 16380) db[outpos++] = '\n';
                    pp += sizeof(struct SpkFileHeader) + fh->size;
                }
            }

            if (dbi < 0)
                fs_create_ex(spkdir, "installed", FS_FILE, 0, 0644);
            fs_write_at(spkdir, "installed", db, outpos);
            free(db);
        }
    }

    free(buf);
    if (err) { print("spk: install failed\n"); return -1; }
    print("Done.\n");
    return 0;
}


int spk_remove(const char *pkgname)
{
    char *db, *out;
    int dbsize, pos, end, outpos = 0;
    int spkdir, found = 0;
    char line[256];
    char name[64];
    int nfiles = 0, i;

    spkdir = spk_root_dir();
    if (spkdir < 0) return -1;

    db = (char *)malloc(16384);
    out = (char *)malloc(16384);
    if (!db || !out) { free(db); free(out); return -1; }

    dbsize = fs_read_at(spkdir, "installed", db, 16384);
    if (dbsize <= 0) {
        free(db); free(out);
        print("spk: no packages installed\n");
        return -1;
    }
    end = dbsize;

    pos = 0;
    while (pos < end) {
        int hdr_start = pos;
        int len = 0;

        while (pos < end && db[pos] != '\n' && len < 255) line[len++] = db[pos++];
        line[len] = '\0';
        if (pos < end) pos++;

        {
            char *lp = line;
            int k = 0;
            while (*lp && *lp != ' ' && k < 63) name[k++] = *lp++;
            name[k] = '\0';
            nfiles = 0;
            while (*lp && *lp != ' ') lp++;
            if (*lp == ' ') lp++;
            while (*lp && *lp != ' ') lp++;
            if (*lp == ' ') lp++;
            while (*lp >= '0' && *lp <= '9') { nfiles = nfiles * 10 + (*lp - '0'); lp++; }
        }

        if (strcmp(name, pkgname) == 0) {
            found = 1;
            
            for (i = 0; i < nfiles; i++) {
                int flen = 0;
                while (pos < end && db[pos] != '\n' && flen < 255)
                    line[flen++] = db[pos++];
                line[flen] = '\0';
                if (pos < end) pos++;

                {
                    int parent;
                    char fname[MAX_NAME];
                    if (resolve_path(line, &parent, fname, MAX_NAME) >= 0) {
                        fs_delete(parent, fname);
                        print("  removed "); print(line); putchar('\n');
                    }
                }
            }
            
        } else {
           
            for (i = hdr_start; i < pos; i++) {
                if (outpos < 16383) out[outpos++] = db[i];
            }
            for (i = 0; i < nfiles; i++) {
                while (pos < end && db[pos] != '\n') {
                    if (outpos < 16383) out[outpos++] = db[pos];
                    pos++;
                }
                if (pos < end) {
                    if (outpos < 16383) out[outpos++] = db[pos];
                    pos++;
                }
            }
        }
    }

    if (!found) {
        free(db); free(out);
        print("spk: package not installed: "); print(pkgname); putchar('\n');
        return -1;
    }

    fs_write_at(spkdir, "installed", out, outpos);

    free(db);
    free(out);
    print("Removed "); print(pkgname); putchar('\n');
    return 0;
}


void spk_list(void)
{
    char *db;
    int dbsize, pos, end;
    int spkdir, count = 0;

    spkdir = spk_root_dir();
    if (spkdir < 0) return;

    db = (char *)malloc(16384);
    if (!db) return;

    dbsize = fs_read_at(spkdir, "installed", db, 16384);
    if (dbsize <= 0) {
        free(db);
        print("spk: no packages installed\n");
        return;
    }
    end = dbsize;

    print("Installed packages:\n");
    pos = 0;
    while (pos < end) {
        char line[256];
        int len = 0;
        int nfiles = 0;
        char *lp;

        while (pos < end && db[pos] != '\n' && len < 255) line[len++] = db[pos++];
        line[len] = '\0';
        if (pos < end) pos++;
        if (len == 0) continue;

        lp = line;
        while (*lp && *lp != ' ') lp++;
        if (*lp == ' ') *lp++ = '\0';
        {
            char ver[16]; int k = 0;
            while (*lp && *lp != ' ' && k < 15) ver[k++] = *lp++;
            ver[k] = '\0';
            if (*lp == ' ') lp++;
            while (*lp >= '0' && *lp <= '9') { nfiles = nfiles * 10 + (*lp - '0'); lp++; }

            print("  "); print(line); print("  "); print(ver); putchar('\n');
            count++;
        }

        while (nfiles-- > 0) {
            while (pos < end && db[pos] != '\n') pos++;
            if (pos < end) pos++;
        }
    }
    if (count == 0) print("  (empty)\n");
    free(db);
}

void spk_info(const char *pkgname)
{
    char *db;
    int dbsize, pos, end;
    int spkdir, found = 0;

    spkdir = spk_root_dir();
    if (spkdir < 0) return;

    db = (char *)malloc(16384);
    if (!db) return;

    dbsize = fs_read_at(spkdir, "installed", db, 16384);
    if (dbsize <= 0) {
        free(db);
        print("spk: no packages installed\n");
        return;
    }
    end = dbsize;

    pos = 0;
    while (pos < end) {
        char line[256];
        int len = 0, nfiles = 0, i;
        char name[64];
        char *lp;

        while (pos < end && db[pos] != '\n' && len < 255) line[len++] = db[pos++];
        line[len] = '\0';
        if (pos < end) pos++;
        if (len == 0) continue;

        lp = line;
        {
            int k = 0;
            while (*lp && *lp != ' ' && k < 63) name[k++] = *lp++;
            name[k] = '\0';
        }
        if (*lp == ' ') lp++;
        {
            char ver[16]; int k = 0;
            while (*lp && *lp != ' ' && k < 15) ver[k++] = *lp++;
            ver[k] = '\0';
            if (*lp == ' ') lp++;
            while (*lp >= '0' && *lp <= '9') { nfiles = nfiles * 10 + (*lp - '0'); lp++; }

            if (strcmp(name, pkgname)) {
                found = 1;
                print("Package: "); print(name); putchar('\n');
                print("Version: "); print(ver); putchar('\n');
                print("Files:   "); print_num(nfiles); putchar('\n');
                for (i = 0; i < nfiles; i++) {
                    int flen = 0;
                    while (pos < end && db[pos] != '\n' && flen < 255)
                        line[flen++] = db[pos++];
                    line[flen] = '\0';
                    if (pos < end) pos++;
                    print("  "); print(line); putchar('\n');
                }
                free(db);
                return;
            }
        }

        while (nfiles-- > 0) {
            while (pos < end && db[pos] != '\n') pos++;
            if (pos < end) pos++;
        }
    }

    if (!found) {
        print("spk: package not installed: "); print(pkgname); putchar('\n');
    }
    free(db);
}