#include <exec/types.h>
#include <exec/io.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <intuition/intuition.h>
#include <workbench/startup.h>
#include <devices/trackdisk.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/intuition.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define error(s) { if (cli) Write(_Backstdout, (s), strlen(s)); }

/* border sizes, shouldn't be hard coded ... */
#define XLEFT 2
#define XRIGHT 2
#define YTOP 10
#define YBOTTOM 1
/* Max/Min window size */
#define MAXHEIGHT (200 - YTOP - YBOTTOM)
#define MAXWIDTH   (640 - XLEFT - XRIGHT)
#define MINWIDTH 200
#define MINHEIGHT 80

/* Detach info */
long _stack = 2000;
char *_procname = "freeblks";
long _priority = 0;
long _BackGroundIO = TRUE;
extern long _Backstdout;

typedef struct FileInfoBlock FIB;
typedef struct InfoData INFO;

extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase *GfxBase;

/* Device access */
struct MsgPort *port;
struct IOStdReq *io;
int DevOpen;
/* Partition characteristics */
long blk_size, blk_offset, root_blk, *secbuf, *secbuf2;
struct Window *win;
/* size of window, of each block, etc */
int xdiv, rectw, recth, width, height;
int cli;

struct NewWindow newwin = {
    0, 0,
    0, 0,
    -1, -1,
    CLOSEWINDOW,
    WINDOWDRAG | WINDOWDEPTH | WINDOWCLOSE | SMART_REFRESH | NOCAREREFRESH | RM
BTRAP,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    0, 0, 0, 0,
    WBENCHSCREEN
};

/* bcpl string -> C string */
char *btoc_str(char *to, BSTR from)
{
    char *cstr = (char *)BADDR(from);

    strncpy(to, cstr + 1, *cstr);
    to[*cstr] = '\0';

    return to;
}

/* Calculate coords on window for block 'block' on disk */
void cvt_point(long *x, long *y, long block)
{
    *x = rectw * (block / (xdiv * height)) + XLEFT;
    *y = recth * (block % height) + YTOP;
}

/* Calc size, & open window */
int prepare_window(struct DeviceNode *dev)
{
    struct FileSysStartupMsg *msg = (struct FileSysStartupMsg *)BADDR(dev->dn_S
tartup);
    ULONG *env = (ULONG *)BADDR(msg->fssm_Environ);
    long blkscyl = env[DE_NUMHEADS] * env[DE_BLKSPERTRACK];
    long numcyls = env[DE_UPPERCYL] - env[DE_LOWCYL] + 1;
    long blks;
    static char title[80];

    if (blkscyl <= MAXHEIGHT) /* Do a "nice" presentation, 1 cylinder per vert
line */
    {
        height = blkscyl;
        xdiv = (numcyls / MAXWIDTH + 1); /* Nb of cylinders per vertical line *
/
        width = numcyls / xdiv;
    }
    else /* Just squash em in */
    {
        blks = numcyls * blkscyl;
        height = MAXHEIGHT;
        xdiv = (blks / MAXHEIGHT + 1) / MAXWIDTH + 1;
        width = (blks / MAXHEIGHT + 1) / xdiv;
    }
    /* Size of rect for 1 block */
    rectw = MINWIDTH / width + 1;
    recth = MINHEIGHT / height + 1;

    /* Open window */
    btoc_str(title, dev->dn_Name);
    strcat(title, ":, free blocks");
    newwin.Title = title;
    newwin.Width = rectw * width + XLEFT + XRIGHT;
    newwin.Height = recth * height + YTOP + YBOTTOM;

    if (win = OpenWindow(&newwin))
    {
        SetAPen(win->RPort, 2);
        RectFill(win->RPort, XLEFT, YTOP, win->Width - XRIGHT - 1, win->Height
- YBOTTOM - 1);
        SetAPen(win->RPort, 3);
        return TRUE;
    }
    return FALSE;
}

/* Reads sector at offset 'sector' from disk ('sector' must be a multiple of 51
2) */
BYTE *ReadSector(long sector, BYTE *buf, long len)
{
    io->io_Command = CMD_READ;
    io->io_Length = len;
    io->io_Data = (APTR)buf;
    io->io_Offset = sector;
    DoIO((struct IORequest *)io);
    return (io->io_Error == 0) ? buf : NULL;
}

/* Turn motor on/off */
void Motor(int on)
{
    io->io_Command = TD_MOTOR;
    io->io_Length = on;
    DoIO((struct IORequest *)io);
}

/* Find device by name */
struct DeviceNode *FindDevice(char *name)
{
    struct DeviceNode *devlist;
    int l = strlen(name), l2;
    char *name2;

    Forbid();
    devlist = (struct DeviceNode *)BADDR(((struct DosInfo *)BADDR(((struct Root
Node *)(DOSBase->dl_Root))->rn_Info))->di_DevInfo);
    for (;devlist; devlist = (struct DeviceNode *)BADDR(devlist->dn_Next))
        if (devlist->dn_Type == DLT_DEVICE)
        {
            name2 = (char *)BADDR(devlist->dn_Name);
            l2 = *name2;
            if (l == l2 && strnicmp(name, name2 + 1, l) == 0) break;
        }

    Permit();
    return devlist;
}

/* Find device by task */
struct DeviceNode *TaskDevice(struct MsgPort *task)
{
    struct DeviceNode *devlist;

    Forbid();
    devlist = (struct DeviceNode *)BADDR(((struct DosInfo *)BADDR(((struct Root
Node *)(DOSBase->dl_Root))->rn_Info))->di_DevInfo);
    for (;devlist; devlist = (struct DeviceNode *)BADDR(devlist->dn_Next))
        if (devlist->dn_Type == DLT_DEVICE)
        {
            if (task == devlist->dn_Task) { Permit(); return devlist; }
        }

    Permit();
    return NULL;
}

/* Reads block n of *partition* */
BYTE *ReadBlock(long *buf, long n)
{
    return(ReadSector((blk_offset + n) * blk_size * 4, (BYTE *)buf, blk_size *
4));
}

/* Get partition characteristics, open device */
int setup(struct DeviceNode *dev)
{
    char devname[32];
    struct FileSysStartupMsg *msg = (struct FileSysStartupMsg *)BADDR(dev->dn_S
tartup);
    ULONG *env = (ULONG *)BADDR(msg->fssm_Environ);
    long err;

    port = CreatePort(0, 0);
    if (!port)
    {
        error("No port!\n");
        return FALSE;
    }
    io = CreateStdIO(port);
    if (!io)
    {
        error("No IO request!\n");
        return FALSE;
    }
    err = OpenDevice(btoc_str(devname, msg->fssm_Device), msg->fssm_Unit, (stru
ct IORequest *)io, msg->fssm_Flags);
    if (err)
    {
        error("Device not opened\n");
        return FALSE;
    }
    DevOpen = TRUE;
    blk_size = env[DE_SIZEBLOCK];
    blk_offset = env[DE_LOWCYL] * env[DE_BLKSPERTRACK] * env[DE_NUMHEADS];
    root_blk = (env[DE_BLKSPERTRACK] * env[DE_NUMHEADS] * (env[DE_UPPERCYL] - e
nv[DE_LOWCYL] + 1) - 1 + env[DE_RESERVEDBLKS]) / 2;
    secbuf = (long *)AllocMem(2 * 4 * blk_size, env[DE_MEMBUFTYPE]);
    if (!secbuf)
    {
        error("No sector buffer!\n");
        return FALSE;
    }
    secbuf2 = secbuf + blk_size;
    return TRUE;
}

/* Close device */
void release(void)
{
    if (secbuf) FreeMem((char *)secbuf, 2 * 4 * blk_size);
    if (DevOpen) CloseDevice((struct IORequest *)io);
    if (io) DeleteStdIO(io);
    if (port) DeletePort(port);
}

/* Displays free blocks on device dev. For OFS/FFS !!! */
void disp_free(struct DeviceNode *dev)
{
    struct FileSysStartupMsg *fmsg = (struct FileSysStartupMsg *)BADDR(dev->dn_
Startup);
    ULONG *env = (ULONG *)BADDR(fmsg->fssm_Environ);
    int quit, bit;
    struct IntuiMessage *msg;
    long first, last, numblks, pos, i, blk, word, x, y;
    int firstx, firsty, lastx, lasty;

    if (prepare_window(dev))
    {
        if (!ReadBlock(secbuf, root_blk)) goto diskerror;
        first = blk_size - 49; last = blk_size - 24; /* extent of bitmap list *
/
        numblks = env[DE_NUMHEADS] * env[DE_BLKSPERTRACK] * (env[DE_UPPERCYL] -
 env[DE_LOWCYL] + 1);
        blk = env[DE_RESERVEDBLKS]; /* First block described in bitmap */
        pos = first;
        lasty = -recth - 1; firsty = -1;

        do {
            if (pos == last) /* End of bitmap table list, read extension bitmap
 table */
            {
                if (!ReadBlock(secbuf, secbuf[last])) goto diskerror;
                pos = first = 0;
                last = blk_size - 1;
            }
            /* Read next bitmap sector */
            if (!ReadBlock(secbuf2, secbuf[pos++])) goto diskerror;

            /* Display free blocks in it */
            for (i = 1; blk != numblks && i < blk_size; i++)
            {
                word = secbuf2[i];
                for (bit = 0; blk != numblks && bit < 32; bit++, blk++)
                {
                    if (word & 1) /* Free sector */
                    {
                        cvt_point(&x, &y, blk);
                        /* Speed up drawing (by a factor of 3 on HD), check
                           for consecutive free blocks & draw them in one go */
     
                        if (y != lasty + recth || x != lastx)
                        {
                            /* Non consecutive blocks, draw */

                            if (firsty != -1)
                                RectFill(win->RPort, firstx, firsty, lastx + re
ctw - 1, lasty + recth - 1);
                            firstx = x; firsty = y;
                        }
                        lastx = x; lasty = y;
                    }
                    word >>= 1; /* next sector */
                }
            }
        } while (blk != numblks);
        if (firsty != -1)
            RectFill(win->RPort, firstx, firsty, lastx + rectw - 1, lasty + rec
th - 1);
        Motor(FALSE); /* Turn drive motor off */

        quit = FALSE;
        while (!quit)
        {
            WaitPort(win->UserPort);
            while (msg = (struct IntuiMessage *)GetMsg(win->UserPort))
            {
                quit = msg->Class == CLOSEWINDOW;
                ReplyMsg((struct Message *)msg);
            }
        }
        CloseWindow(win);
    }
    else error("Couldn't open window\n");
    return;

diskerror:
    CloseWindow(win);
    error("Error reading disk\n");
}

/* Display free blocks on partition passed as parm, from CLI only */
void main(int argc, char **argv)
{
    struct DeviceNode *dev;

    cli = (argc != 0);

    if (IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library"
, 0L))
        if (GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0L))
            if (argc != 2) error("Usage: free <device>\n")
            else
            {
                int l = strlen(argv[1]) - 1;

                if (argv[1][l] != ':') error("Not a device spec!\n")
                else
                {
                    argv[1][l] = '\0';
                    dev = FindDevice(argv[1]);
                    if (dev)
                    {
                        if (setup(dev)) disp_free(dev);
                        release();
                    }
                    else error("No such device\n");
                }
            }
        else error("No graphics library !\n")
    else error("No intuition library !\n");

    if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
    if (GfxBase) CloseLibrary((struct Library *)GfxBase);
    if (_Backstdout) Close(_Backstdout);
}
