/* analyse.c for !DiscEx */


#include "main.h"
#include "dinfo.h"
#include "discmacros.h"
#include "auxui.h"
#include "setup.h"
#include "alloc.h"
#include "map.h"
#include "ansctr.h"
#include "analyse.h"
#include "tree.h"


/* component IDs for analysis window */
#define  ID_A_FS                       0x1f
#define  ID_A_Drive                    0x1e

#define  ID_SUMM_riscosdiscsize        0x4f
#define  ID_SUMM_bootblocksize         0x56
#define  ID_SUMM_bootblocksize_PC      0x58
#define  ID_SUMM_mapsize               0x5a
#define  ID_SUMM_mapsize_PC            0x5c
#define  ID_SUMM_badblocks             0x5f
#define  ID_SUMM_badblocksize          0x5e
#define  ID_SUMM_badblocksize_PC       0x60
#define  ID_SUMM_allocfrags            0x63
#define  ID_SUMM_allocfragsize         0x62
#define  ID_SUMM_allocfragsize_PC      0x64
#define  ID_SUMM_freefrags             0x67
#define  ID_SUMM_freefragsize          0x66
#define  ID_SUMM_freefragsize_PC       0x68

#define  ID_ALL_allocfragsize          0x7e
#define  ID_ALL_files                  0x6c
#define  ID_ALL_filesize               0x6b
#define  ID_ALL_filesize_PC            0x6d
#define  ID_ALL_dirs                   0x73
#define  ID_ALL_dirsize                0x72
#define  ID_ALL_dirsize_PC             0x74
#define  ID_ALL_notusedsize            0x76
#define  ID_ALL_notusedsize_PC         0x78
#define  ID_ALL_finalsectorwaste       0x7a
#define  ID_ALL_finalsectorwaste_PC    0x7c

#define  ID_FRAG_allocfrags            0x94
#define  ID_FRAG_allocfragsize         0x92
#define  ID_FRAG_ssfrags               0x82
#define  ID_FRAG_ssfragsize            0x81
#define  ID_FRAG_ssfragsize_PC         0x83
#define  ID_FRAG_sufrags               0x89
#define  ID_FRAG_sufragsize            0x88
#define  ID_FRAG_sufragsize_PC         0x8a
#define  ID_FRAG_mufrags               0x95
#define  ID_FRAG_mufragsize            0x8c
#define  ID_FRAG_mufragsize_PC         0x8d
#define  ID_FRAG_ukfrags               0x96
#define  ID_FRAG_ukfragsize            0x8f
#define  ID_FRAG_ukfragsize_PC         0x90

#define  ID_SSW_ssfrags                0xbe
#define  ID_SSW_ssfragsize             0xbc
#define  ID_SSW_ssw1                   0xaf
#define  ID_SSW_ssw1size               0xae
#define  ID_SSW_ssw1size_PC            0xb0
#define  ID_SSW_ssw2                   0xb6
#define  ID_SSW_ssw2size               0xb5
#define  ID_SSW_ssw2size_PC            0xb7
#define  ID_SSW_ssw3                   0xbf
#define  ID_SSW_ssw3size               0xb9
#define  ID_SSW_ssw3size_PC            0xba

#define  ID_SUW_sufrags                0xab
#define  ID_SUW_sufragsize             0xa9
#define  ID_SUW_suw1                   0x99
#define  ID_SUW_suw1size               0x98
#define  ID_SUW_suw1size_PC            0x9a
#define  ID_SUW_suw2                   0xa0
#define  ID_SUW_suw2size               0x9f
#define  ID_SUW_suw2size_PC            0xa1
#define  ID_SUW_suw3                   0xac
#define  ID_SUW_suw3size               0xa3
#define  ID_SUW_suw3size_PC            0xa4

#define  ID_MUW_mufrags                0xd1
#define  ID_MUW_mufragsize             0xcf
#define  ID_MUW_muw1                   0xc2
#define  ID_MUW_muw1size               0xc1
#define  ID_MUW_muw1size_PC            0xc3
#define  ID_MUW_muw2                   0xc9
#define  ID_MUW_muw2size               0xc8
#define  ID_MUW_muw2size_PC            0xca
#define  ID_MUW_muw3                   0xd2
#define  ID_MUW_muw3size               0xcc
#define  ID_MUW_muw3size_PC            0xcd
#define  ID_MUW_muw4                   0xd6
#define  ID_MUW_muw4size               0xd3
#define  ID_MUW_muw4size_PC            0xd4

/* component IDs for analysis window's menu */
#define  ID_MENU_details    1
#define  ID_MENU_analysis   0


/* This record contains the statistics for an "analysis" window */
typedef struct _Stats {

    unsigned bootblocksize;     /* sectors */
    unsigned mapsize;           /* sectors */
    int badblocks;
    unsigned badblocksize;      /* sectors */
    int allocfrags;
    unsigned allocfragsize;     /* sectors */
    int freefrags;
    unsigned freefragsize;      /* sectors */

    int files;
    unsigned filesize;          /* sectors */
    int dirs;
    unsigned finalsectorwaste;  /* in bytes */

    int ssfrags;
    unsigned ssfragsize;        /* sectors */
    int sufrags;
    unsigned sufragsize;        /* sectors */
    int mufrags;
    unsigned mufragsize;        /* sectors */
    int ukfrags;
    unsigned ukfragsize;        /* sectors */

    /* wastage analysis for shared fragments - in sectors */
    int ssw1;
    unsigned ssw1size;          /* "minimum increment" wastage */
    int ssw2;
    unsigned ssw2size;          /* "minimum fragment size" wastage */
    int ssw3;
    unsigned ssw3size;          /* "excess allocation" wastage */

    /* wastage analysis for single-chunk unshared fragments - in sectors */
    int suw1;
    unsigned suw1size;          /* "minimum increment" wastage */
    int suw2;
    unsigned suw2size;          /* "minimum fragment size" wastage */
    int suw3;
    unsigned suw3size;          /* "excess allocation" wastage */

    /* wastage analysis for multi-chunk unshared fragments - in sectors */
    int muw1;
    unsigned muw1size;          /* "minimum increment" wastage */
    int muw2;
    unsigned muw2size;          /* "minimum fragment size" wastage */
    int muw3;
    unsigned muw3size;          /* "excess allocation" wastage */
    int muw4;
    unsigned muw4size;          /* "unused chunks" wastage */

} StatsRec;


/* holds the filenames to be used for saving */
static char detailsfn[256] = "details";
static char summaryfn[256] = "summary";


/*
 * The array frag[..] contains an entry for each fragment id which addresses
 *  a FragRec (or is NULL is the fragment id is not in use).
 *
 * Each FragRec contains:
 *  - the sector address of the fragment on disc
 *  - the size of the fragment (in sectors)
 *  - the type of fragment (packed with the length):
 *
 *    FTYPE_UK:    (unknown)
 *      No further information
 *
 *    FTYPE_SU:    (contains a single-fragment unshared object)
 *      - points to the object's ForDRec
 *
 *    FTYPE_MU:    (contains a multi-fragment unshared object)
 *      - points to the object's ForDRec
 *      - points to a list of ExtraFragRec's, each of which contains the
 *         sector address and size in sectors of one of the fragments; these
 *         are stored in (cyclic) ascending order of sector address, starting
 *         with the fragment in the "home zone"
 *
 *    FTYPE_SS:    (contains a number of single-fragment shared objects)
 *      - points to a list of ForDListRec's, each of which identifies an
 *         object's ForDRec; these are held in order of increasing sector
 *         offset within the fragment.
 */

#define  FTYPE_UK     0  /* unknown (as yet) */
#define  FTYPE_SU     1  /* single-fragment unshared object */
#define  FTYPE_SS     2  /* single-fragment shared object */
#define  FTYPE_MU     3  /* multi-fragment unshared object */

typedef struct
{
    unsigned len:30;     /* in sectors */
    unsigned type:2;
} LenFlag;

typedef struct _extrafrag
{
    struct _extrafrag *next;
    unsigned sector;           /* absolute disc address (sectors) */
    unsigned len;              /* in sectors */
} ExtraFragRec, *ExtraFragPtr;

typedef struct _fordlist
{
    struct _fordlist *next;
    ForDPtr ford;
} ForDListRec, *ForDListPtr;

typedef struct _Frag
{
    ExtraFragPtr next;
    unsigned sector;
    LenFlag v;
    union
    {
        ForDPtr ford;
        ForDListPtr fordlist;
    } u;
} FragRec;


/*
 * The following variables are accessed globally: they are set up by
 *  analyse_analyse(..) each time it is called.
 */

static StatsPtr t;        /* the statistics record */
static DiscInfoPtr di;    /* the disc information record */
static DiscRecordPtr dr;  /* the disc record */
static FragPtr *frag;     /* the fragment index */
static BigBlockDefPtr a;  /* the allocator */
static int drive;         /* the drive number */


/*
 * Used to pass data from scan_update_analysis_dbox(..) to show(..) and
 *  showPC(..).
 */

static ObjectId analysedbox;



static error * showPC (ComponentId id, unsigned val1, unsigned val2)
{
    return displayfield_set_value (0, analysedbox, id, pc (val1, val2));
}


static error * show (ComponentId id, unsigned val)
{
    return displayfield_set_value (0, analysedbox, id, num (val));
}


/*
 * Copy statistics to the "analysis" dbox prior to showing it.
 */

error * analyse_update_dbox (ScanListPtr p)
{
    StatsPtr t = p->stats;
    DiscInfoPtr di = p->di;
    DiscRecordPtr dr = &di->dr;

    int dirsize = (di->secsize > 2048) ? t->dirs :
                                         t->dirs << (11 - dr->log2secsize);
    int fsw = (t->finalsectorwaste +
                      (di->secsize/2)) >> dr->log2secsize; /* rounded */
    int notusedsize = t->allocfragsize - (t->filesize + dirsize);

    /* set up global variable for show(..) and showPC(..) */
    analysedbox = p->dbox;

    displayfield_set_value (0, analysedbox, ID_A_FS,
                                    (p->FS  == FS_ADFS) ? "ADFS" : "SCSIFS");
    show (ID_A_Drive, p->drive);

    show (ID_SUMM_riscosdiscsize, di->riscosdiscsize);
    show (ID_SUMM_bootblocksize, t->bootblocksize);
    showPC (ID_SUMM_bootblocksize_PC, t->bootblocksize, di->riscosdiscsize);
    show (ID_SUMM_mapsize, t->mapsize);
    showPC (ID_SUMM_mapsize_PC, t->mapsize, di->riscosdiscsize); 
    show (ID_SUMM_badblocks, t->badblocks);
    show (ID_SUMM_badblocksize, t->badblocksize);
    showPC (ID_SUMM_badblocksize_PC, t->badblocksize, di->riscosdiscsize);
    show (ID_SUMM_allocfrags, t->allocfrags);
    show (ID_SUMM_allocfragsize, t->allocfragsize);
    showPC (ID_SUMM_allocfragsize_PC, t->allocfragsize, di->riscosdiscsize);
    show (ID_SUMM_freefrags, t->freefrags);
    show (ID_SUMM_freefragsize, t->freefragsize);
    showPC (ID_SUMM_freefragsize_PC, t->freefragsize, di->riscosdiscsize);

    show (ID_ALL_allocfragsize, t->allocfragsize);
    show (ID_ALL_files, t->files);
    show (ID_ALL_filesize, t->filesize);
    showPC (ID_ALL_filesize_PC, t->filesize, t->allocfragsize);
    show (ID_ALL_dirs, t->dirs);
    show (ID_ALL_dirsize, dirsize);
    showPC (ID_ALL_dirsize_PC, dirsize, t->allocfragsize);
    show (ID_ALL_notusedsize, notusedsize);
    showPC (ID_ALL_notusedsize_PC, notusedsize, t->allocfragsize);
    show (ID_ALL_finalsectorwaste, fsw);
    showPC (ID_ALL_finalsectorwaste_PC, fsw, t->allocfragsize);

    show (ID_FRAG_allocfrags, t->allocfrags);
    show (ID_FRAG_allocfragsize, t->allocfragsize);
    show (ID_FRAG_ssfrags, t->ssfrags);
    show (ID_FRAG_ssfragsize, t->ssfragsize);
    showPC (ID_FRAG_ssfragsize_PC, t->ssfragsize, t->allocfragsize);
    show (ID_FRAG_sufrags, t->sufrags);
    show (ID_FRAG_sufragsize, t->sufragsize);
    showPC (ID_FRAG_sufragsize_PC, t->sufragsize, t->allocfragsize);
    show (ID_FRAG_mufrags, t->mufrags);
    show (ID_FRAG_mufragsize, t->mufragsize);
    showPC (ID_FRAG_mufragsize_PC, t->mufragsize, t->allocfragsize);
    show (ID_FRAG_ukfrags, t->ukfrags);
    show (ID_FRAG_ukfragsize, t->ukfragsize);
    showPC (ID_FRAG_ukfragsize_PC, t->ukfragsize, t->allocfragsize);

    show (ID_SSW_ssfrags, t->ssfrags);
    show (ID_SSW_ssfragsize, t->ssfragsize);
    show (ID_SSW_ssw1, t->ssw1);
    show (ID_SSW_ssw1size, t->ssw1size);
    showPC (ID_SSW_ssw1size_PC, t->ssw1size, t->ssfragsize);
    show (ID_SSW_ssw2, t->ssw2);
    show (ID_SSW_ssw2size, t->ssw2size);
    showPC (ID_SSW_ssw2size_PC, t->ssw2size, t->ssfragsize);
    show (ID_SSW_ssw3, t->ssw3);
    show (ID_SSW_ssw3size, t->ssw3size);
    showPC (ID_SSW_ssw3size_PC, t->ssw3size, t->ssfragsize);

    show (ID_SUW_sufrags, t->sufrags);
    show (ID_SUW_sufragsize, t->sufragsize);
    show (ID_SUW_suw1, t->suw1);
    show (ID_SUW_suw1size, t->suw1size);
    showPC (ID_SUW_suw1size_PC, t->suw1size, t->sufragsize);
    show (ID_SUW_suw2, t->suw2);
    show (ID_SUW_suw2size, t->suw2size);
    showPC (ID_SUW_suw2size_PC, t->suw2size, t->sufragsize);
    show (ID_SUW_suw3, t->suw3);
    show (ID_SUW_suw3size, t->suw3size);
    showPC (ID_SUW_suw3size_PC, t->suw3size, t->sufragsize);

    show (ID_MUW_mufrags, t->mufrags);
    show (ID_MUW_mufragsize, t->mufragsize);
    show (ID_MUW_muw1, t->muw1);
    show (ID_MUW_muw1size, t->muw1size);
    showPC (ID_MUW_muw1size_PC, t->muw1size, t->mufragsize);
    show (ID_MUW_muw2, t->muw2);
    show (ID_MUW_muw2size, t->muw2size);
    showPC (ID_MUW_muw2size_PC, t->muw2size, t->mufragsize);
    show (ID_MUW_muw3, t->muw3);
    show (ID_MUW_muw3size, t->muw3size);
    showPC (ID_MUW_muw3size_PC, t->muw3size, t->mufragsize);
    show (ID_MUW_muw4, t->muw4);
    show (ID_MUW_muw4size, t->muw4size);
    showPC (ID_MUW_muw4size_PC, t->muw4size, t->mufragsize);

    return NULL;
}


static error * create_fragrec (FragPtr *p)
{
    int size = sizeof (FragRec);

    if (a->nextfree + size >= a->endfree)
        ER ( alloc_big_block (a) );

    *p = (FragPtr) a->nextfree;
    memset (a->nextfree, 0, size);
    a->nextfree += size;

    return NULL;
}


static error * create_extrafragrec (ExtraFragPtr *p)
{
    int size = sizeof (ExtraFragRec);

    if (a->nextfree + size >= a->endfree)
        ER ( alloc_big_block (a) );

    *p = (ExtraFragPtr) a->nextfree;
    memset (a->nextfree, 0, size);
    a->nextfree += size;

    return NULL;
}


static error * create_fordlistrec (ForDListPtr *p)
{
    int size = sizeof (ForDListRec);

    if (a->nextfree + size >= a->endfree)
        ER ( alloc_big_block (a) );

    *p = (ForDListPtr) a->nextfree;
    memset (a->nextfree, 0, size);
    a->nextfree += size;

    return NULL;
}


/*
 * Called to make sure that the list of fragments addressed by f starts
 *  with the fragment that is in the home zone.
 */

static error * check_extra_frag_order (FragPtr f, unsigned homezoneaddr)
{
    ExtraFragPtr p = f->next;
    ExtraFragPtr beforep, afterp;

    if (f->sector >= homezoneaddr)
        return NULL;

    while (p->sector < homezoneaddr)
    {
        if (p->next == NULL)
            return error_lookup ("BadMulti");
        p = p->next;
    }

    beforep = f->next;
    afterp = p->next;

    /* swap contents of the FragRec and the ExtraFragRec p */
    {
        unsigned len = p->len;
        unsigned sector = p->sector;

        p->len = f->v.len;
        p->sector = f->sector;

        f->v.len = len;
        f->sector = sector;
    }

    if (afterp != NULL)
    {
        f->next = afterp;
        while (afterp->next != NULL)
            afterp = afterp->next;
        afterp->next = p;
    }
    else
        f->next = p;

    if (beforep != p)
    {
        p->next = beforep;
        while (beforep->next != p)
            beforep = beforep->next;
        beforep->next = NULL;
    }
    else
        p->next = NULL;

    return NULL;
}


static error * record_frag
(
    DiscInfoPtr di,           /* disc information record */
    int zone,                 /* zone in which fragment lies */
    int fragid,               /* -1 => free */
    int pos,                  /* alloc units from zone start */
    int len,                  /* in alloc units */
    void *handle
)
{
    if (fragid == -1)  /* free fragment */
    {
        t->freefrags++;
        t->freefragsize += len;

        return NULL;
    }

    /* it's an allocated fragment */
    if (frag[fragid] == NULL)  /* the first occurrence */
    {
        FragPtr p;

        ER ( create_fragrec (&p) );

        pos += ZONE_START (di, zone);
        p->sector = SECS_IN_ALLOCS (di, pos);
        p->v.len = SECS_IN_ALLOCS (di, len);
        frag[fragid] = p;

        return NULL;
    }

    /* not the first occurrence */
    {
        ExtraFragPtr p;
        ExtraFragPtr *prev = &frag[fragid]->next;

        ER ( create_extrafragrec (&p) );

        /* append to end of list */
        while (*prev != NULL)
            prev = &(*prev)->next;
        *prev = p;

        pos += ZONE_START (di, zone);
        p->sector = SECS_IN_ALLOCS (di, pos);
        p->len = SECS_IN_ALLOCS (di, len);
    }

    return NULL;
}


static error * record_allocations (ForDPtr object, void *handle)
{
    int fragid = tree_get_fragid (object);
    int sector = tree_get_sector (object);
    Bool isdir = tree_isdir (object);
    Bool shared = sector != 0;
    FragPtr f;
    ForDListPtr fel;

    /* update statistics */
    if (isdir)
        t->dirs++;
    else
    {
        int len = tree_get_len (object);
        int extrabit = len & (di->secsize - 1);

        t->files++;
        t->filesize += SECS_FOR (di, len);
        if (extrabit != 0)
            t->finalsectorwaste += (di->secsize - extrabit);
    }

    if (fragid == 0)
    {
        if (isdir || tree_get_len (object) != 0)
            return error_lookup ("BadEmptyObj", tree_get_name (object));

        return NULL;
    }

    if (fragid >= di->maxfrags)
        return error_lookup ("BadFragId", tree_get_name (object), fragid);

    f = frag[fragid];
    if (f == NULL)
        return error_lookup ("UnallocObj", tree_get_name (object), fragid);

    switch (f->v.type)
    {
    case FTYPE_UK:    /* no object allocated to this fragment yet */
        if (shared)
        {
            /* only fragment number 2 is allowed to have more than one piece
               and also to be shared */
            if (f->next != NULL && fragid != 2)
                return error_lookup ("BadFragUse2",
                                         tree_get_name (object), fragid);

            f->v.type = FTYPE_SS;
            ER ( create_fordlistrec (&fel) );
            f->u.fordlist = fel;
            fel->ford = object;
        }
        else
        {
            f->v.type = (f->next == NULL) ? FTYPE_SU : FTYPE_MU;
            f->u.ford = object;
        }
        break;

    case FTYPE_SU:    /* unshared obect allocated to this fragment */
    case FTYPE_MU:
        return error_lookup ("BadFragUse", tree_get_name (object),
                                       tree_get_name (f->u.ford), fragid);
        break;

    case FTYPE_SS:    /* shared object(s) allocated to this fragment */
        if (!shared)
            return error_lookup ("BadFragUse",
                                     tree_get_name (f->u.fordlist->ford),
                                     tree_get_name (object), fragid);

        ER ( create_fordlistrec (&fel) );
        fel->ford = object;

        /* insert new element into list in sector order */
        {
            ForDListPtr p = f->u.fordlist;
            ForDListPtr prev = NULL;

            while (p != NULL && sector > tree_get_sector (p->ford))
            {
                if (prev == NULL)
                    prev = p;
                else
                    prev = prev->next;
                p = p->next;
            }

            if (prev == NULL)
                f->u.fordlist = fel;
            else
                prev->next = fel;
            fel->next = p;
        }

        break; 
    }

    return NULL;
}


/*
 * Checks that the object described by p fits into the multi-chunk fragment
 *  f whose size is 'fragsize'.
 *
 * Updates statistics as well.
 */

static error * process_fordmu (ForDPtr p, FragPtr f, int fragsize)
{
    int len = (tree_isdir (p)) ? 2048 : tree_get_len (p);
    int slen = SECS_FOR (di, len);
    int alen = UNSHARESIZE_FOR (di, slen);
    ExtraFragPtr ef = f->next;

    if (alen > fragsize)
        return error_lookup ("UUOverflow", tree_get_fragid (p));

    if (alen > slen)
    {
        t->muw1++;
        t->muw1size += (alen - slen);
    }

    /* move to the last chunk that is occupied by any part of this object */
    alen -= f->v.len;
    fragsize = ef->len;
    while (alen > fragsize)
    {
        alen -= fragsize;
        ef = ef->next;
        fragsize = ef->len;
    }

    if (alen < di->minfragsize)
    {
        t->muw2++;
        t->muw2size += (di->minfragsize - alen);
    }

    if (fragsize > di->minfragsize && alen < fragsize)
    {
        t->muw3++;
        t->muw3size += (fragsize -
                          (alen < di->minfragsize ? di->minfragsize : alen));
    }

    /* are there any chunks totally unoccupied? */
    while (TRUE)
    {
        ef = ef->next;
        if (ef == NULL)
            break;

        t->muw4++;
        t->muw4size += ef->len;
    }

    return NULL;
}


/*
 * Checks that the object described by p fits into the single-chunk fragment
 *  whose size is 'fragsize'.
 *
 * Updates statistics as well.
 */

static error * process_fordsu (ForDPtr p, int fragsize)
{
    int len = (tree_isdir (p)) ? 2048 : tree_get_len (p);
    int slen = SECS_FOR (di, len);
    int alen = UNSHARESIZE_FOR (di, slen);

    if (alen > fragsize)
        return error_lookup ("UUOverflow", tree_get_fragid (p));

    if (alen > slen)
    {
        t->suw1++;
        t->suw1size += (alen - slen);
    }

    if (alen < di->minfragsize)
    {
        t->suw2++;
        t->suw2size += (di->minfragsize - alen);
    }

    if (fragsize > di->minfragsize && alen < fragsize)
    {
        t->suw3++;
        t->suw3size += (fragsize -
                          (alen < di->minfragsize ? di->minfragsize : alen));
    }

    return NULL;
}


/*
 * Checks that the objects described in the ordered ForDList p neither
 *  overlap nor overflow the fragment in which they are stored. This fragment
 *  has size 'fragsize', and the first shareunit available for allocation
 *  is at sector 'offset' (only non-zero for fragment 2).
 *
 * Updates statistics as well.
 */

static error * process_fordlist
(
    ForDListPtr p,
    int fragsize,
    int offset
)
{
    int totsize = offset;

    while (p != NULL)
    {
        int sector = (tree_get_sector (p->ford) - 1) << dr->shareshift;
        int len = (tree_isdir (p->ford)) ? 2048 : tree_get_len (p->ford);
        int slen = SECS_FOR (di, len);
        int salen = SHARESIZE_FOR (di, slen);

        if (sector < offset)
            return error_lookup ("Overlap", tree_get_fragid (p->ford));
        offset = sector + salen;
        if (offset > fragsize)
            return error_lookup ("Overflow", tree_get_fragid (p->ford));

        totsize += salen;
        if (salen > slen)
        {
            t->ssw1++;
            t->ssw1size += (salen - slen);
        }

        p = p->next;
    }

    if (totsize < di->minfragsize)
    {
        t->ssw2++;
        t->ssw2size += (di->minfragsize - totsize);
    }

    if (fragsize > di->minfragsize && totsize < fragsize)
    {
        t->ssw3++;
        t->ssw3size += (fragsize -
                    (totsize < di->minfragsize ? di->minfragsize : totsize));
    }

    return NULL;
}


static error * get_stats (void)
{
    int fragid;
    FragPtr f;
    ExtraFragPtr p;
    int mapfraglen;

    /* process the bad block list (fragment 1) */
    t->badblocks = t->badblocksize = 0;
    f = frag[1];
    if (f != NULL)
    {
        if (f->v.type != FTYPE_UK)
            return error_lookup ("BadFrag1");

        t->badblocks = 1;
        t->badblocksize = f->v.len;

        p = f->next;
        while (p != NULL)
        {
            t->badblocks++;
            t->badblocksize += p->len;
            p = p->next;
        }
    }

    /*
     * Process fragment 2:
     *  If a floppy disc, then this contains the maps, the root directory,
     *   and maybe some files in the root.
     *  If a hard disc, then this also contains the boot block.
     */

    f = frag[2];
    if (f == NULL)
        return error_lookup ("NoFrag2");

    p = f->next;
    if (drive < 4)    /* floppy */
    {
        if (p != NULL)
            return error_lookup ("BadFloppyFrag2");
        mapfraglen = f->v.len;
        t->bootblocksize = 0;
    }
    else              /* hard disc */
    {
        t->bootblocksize = f->v.len;
        if (p == NULL || p->next != NULL)
            return error_lookup ("BadHardFrag2");
        mapfraglen = p->len;
    }

    t->mapsize = 2 * dr->nzones;  /* two copies of the map */

    t->ssfrags = t->allocfrags = 1;
    t->ssfragsize = t->allocfragsize = mapfraglen - t->mapsize;
    t->sufrags = t->sufragsize = 0;
    t->mufrags = t->mufragsize = 0;
    t->ukfrags = t->ukfragsize = 0;

    {
        int samapsize = SHARESIZE_FOR (di, t->mapsize);

        if (samapsize > t->mapsize)
        {
            t->ssw1 = 1;
            t->ssw1size = samapsize - t->mapsize;
        }
        else
            t->ssw1 = t->ssw1size = 0;

        t->ssw2 = t->ssw3 = 0;
        t->ssw2size = t->ssw3size = 0;
        ER ( process_fordlist (f->u.fordlist, mapfraglen, samapsize) );
    }

    t->suw1 = t->suw2 = t->suw3 = 0;
    t->suw1size = t->suw2size = t->suw3size = 0;
    t->muw1 = t->muw2 = t->muw3 = t->muw4 = 0;
    t->muw1size = t->muw2size = t->muw3size = t->muw4size = 0;

    /* scan through the other fragments */
    for (fragid = 3; fragid < di->maxfrags; fragid++)
    {
        f = frag[fragid];

        if (f != NULL)
        {
            int size = f->v.len;

            t->allocfrags++;
            p = f->next;
            while (p != NULL)
            {
                size += p->len;
                p = p->next;
            }
            t->allocfragsize += size;

            switch (f->v.type)
            {
            case FTYPE_SU:
                t->sufrags++;
                t->sufragsize += size;
                ER ( process_fordsu (f->u.ford, size) );
                break;
            case FTYPE_MU:
                t->mufrags++;
                t->mufragsize += size;
                ER ( process_fordmu (f->u.ford, f, size) );
                break;
            case FTYPE_SS:
                t->ssfrags++;
                t->ssfragsize += size;
                ER ( process_fordlist (f->u.fordlist, size, 0) );
                break;
            case FTYPE_UK:
                t->ukfrags++;
                t->ukfragsize += size;
                break;
            }
        }
    }

    return NULL;
}


/*
 * Called when "gathering" phase is complete in order to calculate the
 *  required statistics.
 *
 * This function allocates the statistics record and the fragment index; both
 *  are released by the function scan_delete(..) when the analysis dbox is
 *  closed.
 */

error * analyse_analyse (ScanListPtr p)
{
    int zone;

    /* allocate the statistics record */
    ER ( check_alloc ((void **) &p->stats, sizeof (StatsRec)) );

    /* allocate and clear space for the fragment index */
    ER ( check_alloc ((void **) &p->frag,
                                p->di->maxfrags * sizeof (FragPtr)) );

    /* initialise shared "global" variables for the analysis routines */
    di = p->di;
    dr = &di->dr;
    frag = p->frag;
    t = p->stats;
    a = p->alloc;
    drive = p->drive;

    /* read map into memory */
    ER ( map_read (drive, di) );

    /*
     * Scan the map and create FragRecs for each allocated fragment, and also
     *  record statistics on the free fragments.
     */

    t->freefragsize = t->freefrags = 0;
    for (zone = 0; zone < dr->nzones; zone++)
    {
        char *buf;

        ER ( map_find_zone (di, zone, &buf) );
        ER ( map_scan_block (di,                 /* disc info record ptr */
                             buf,                /* map block address */
                             zone,               /* zone number */
                             record_frag,        /* call for each fragment */
                             NULL
                            ) );
    }
    t->freefragsize = SECS_IN_ALLOCS (di, t->freefragsize);

    /*
     * Make sure that all multi-piece objects start with the bit that's in
     *  the home zone
     */

    for (zone = 0; zone < dr->nzones; zone++)
    {
        unsigned zonestart = SECS_IN_ALLOCS (di, ZONE_START (di, zone));
        int fragid;
        int minfragid = zone * di->idsperzone;

        for (fragid = minfragid;
                      fragid < minfragid + di->idsperzone; fragid++)
        {
            FragPtr f = frag[fragid];

            if (f != NULL && f->next != NULL)
                ER ( check_extra_frag_order (f, zonestart) );
        }
    }

    /* map no longer required - so free it */
    map_free ();

    /*
     * Scan directory hierarchy to link objects and fragments, and to record
     *  file and directory statistics.
     */

    t->files = t->filesize = t->dirs = t->finalsectorwaste = 0;
    ER ( tree_walk (p, record_allocations, NULL) );

    /* gather remaining statistics */
    ER ( get_stats () );

    return NULL;
}


/*
 * Print the summary to a file.
 */

static error * analyse_print_summary (FILE *f, void *h)
{
    ScanListPtr p = (ScanListPtr) h;
    DiscInfoPtr di = p->di;
    DiscRecordPtr dr = &di->dr;
    StatsPtr t = p->stats;

    int dirsize = (di->secsize > 2048) ? t->dirs :
                                         t->dirs << (11 - dr->log2secsize);
    int fsw = (t->finalsectorwaste +
                      (di->secsize/2)) >> dr->log2secsize; /* rounded */
    int notusedsize = t->allocfragsize - (t->filesize + dirsize);

    char *ff = "%33s  %7s  %10s  %5s\n";
    char *z = "";

    fprintf (f, "Analysis of space usage for %s::%d\n\n",
                    p->FS == FS_ADFS ? "ADFS" : "SCSI", p->drive);

    fprintf(f, "SUMMARY\n");
    fprintf(f, ff, z, "Count", "Sectors", "% ");
    fprintf(f, ff, "RISC OS disc size", z,
                       num(di->riscosdiscsize), z);
    fprintf(f, ff, "Boot block", z, 
                       num(t->bootblocksize),
                       pc(t->bootblocksize, di->riscosdiscsize));
    fprintf(f, ff, "FileCore maps", z, 
                       num(t->mapsize),
                       pc(t->mapsize, di->riscosdiscsize));
    fprintf(f, ff, "Bad blocks",
                       num(t->badblocks), 
                       num(t->badblocksize),
                       pc(t->badblocksize, di->riscosdiscsize));
    fprintf(f, ff, "Allocated fragments",
                       num(t->allocfrags), 
                       num(t->allocfragsize),
                       pc(t->allocfragsize, di->riscosdiscsize));
    fprintf(f, ff, "Free fragments",
                       num(t->freefrags), 
                       num(t->freefragsize),
                       pc(t->freefragsize, di->riscosdiscsize));

    fprintf(f, "\nALLOCATED SPACE\n");
    fprintf(f, ff, z, "Count", "Sectors", "% ");
    fprintf(f, ff, "Allocated fragments", z, 
                       num(t->allocfragsize), z);
    fprintf(f, ff, "Files",
                       num(t->files), 
                       num(t->filesize),
                       pc(t->filesize, t->allocfragsize));
    fprintf(f, ff, "Directories",
                       num(t->dirs), 
                       num(dirsize),
                       pc(dirsize, t->allocfragsize));
    fprintf(f, ff, "Not used", z,
                       num(notusedsize),
                       pc(notusedsize, t->allocfragsize));
    fprintf(f, ff, "'Final sector' wastage in files", z,
                       num(fsw),
                       pc(fsw, t->allocfragsize));

    fprintf(f, "\nALLOCATED FRAGMENT ANALYSIS\n");
    fprintf(f, ff, z, "Count", "Sectors", "% ");
    fprintf(f, ff, "Allocated fragments",
                       num(t->allocfrags), 
                       num(t->allocfragsize), z);
    fprintf(f, ff, "Shared fragments",
                       num(t->ssfrags), 
                       num(t->ssfragsize),
                       pc(t->ssfragsize, t->allocfragsize));
    fprintf(f, ff, "'Single-chunk' unshared fragments",
                       num(t->sufrags), 
                       num(t->sufragsize),
                       pc(t->sufragsize, t->allocfragsize));
    fprintf(f, ff, "'Multi-chunk' unshared fragments",
                       num(t->mufrags), 
                       num(t->mufragsize),
                       pc(t->mufragsize, t->allocfragsize));
    fprintf(f, ff, "'Lost' fragments",
                       num(t->ukfrags), 
                       num(t->ukfragsize),
                       pc(t->ukfragsize, t->allocfragsize));

    fprintf(f, "\nANALYSIS OF WASTE IN SHARED FRAGMENTS\n");
    fprintf(f, ff, z, "Count", "Sectors", "% ");
    fprintf(f, ff, "Shared fragments",
                       num(t->ssfrags), 
                       num(t->ssfragsize), z);
    fprintf(f, ff, "'Minimum increment' wastage",
                       num(t->ssw1), 
                       num(t->ssw1size),
                       pc(t->ssw1size, t->ssfragsize));
    fprintf(f, ff, "'Minimum fragment size' wastage",
                       num(t->ssw2), 
                       num(t->ssw2size),
                       pc(t->ssw2size, t->ssfragsize));
    fprintf(f, ff, "'Excess allocation' wastage",
                       num(t->ssw3), 
                       num(t->ssw3size),
                       pc(t->ssw3size, t->ssfragsize));

    fprintf(f, "\nANALYSIS OF WASTE IN 'SINGLE-CHUNK' UNSHARED FRAGMENTS\n");
    fprintf(f, ff, z, "Count", "Sectors", "% ");
    fprintf(f, ff, "'Single-chunk' unshared fragments",
                       num(t->sufrags), 
                       num(t->sufragsize), z);
    fprintf(f, ff, "'Minimum increment' wastage",
                       num(t->suw1), 
                       num(t->suw1size),
                       pc(t->suw1size, t->sufragsize));
    fprintf(f, ff, "'Minimum fragment size' wastage",
                       num(t->suw2), 
                       num(t->suw2size),
                       pc(t->suw2size, t->sufragsize));
    fprintf(f, ff, "'Excess allocation' wastage",
                       num(t->suw3), 
                       num(t->suw3size),
                       pc(t->suw3size, t->sufragsize));

    fprintf(f, "\nANALYSIS OF WASTE IN 'MULTI-CHUNK' UNSHARED FRAGMENTS\n");
    fprintf(f, ff, z, "Count", "Sectors", "% ");
    fprintf(f, ff, "'Multi-chunk' unshared fragments",
                       num(t->mufrags), 
                       num(t->mufragsize), z);
    fprintf(f, ff, "'Minimum increment' wastage",
                       num(t->muw1), 
                       num(t->muw1size),
                       pc(t->muw1size, t->mufragsize));
    fprintf(f, ff, "'Minimum fragment size' wastage",
                       num(t->muw2), 
                       num(t->muw2size),
                       pc(t->muw2size, t->mufragsize));
    fprintf(f, ff, "'Excess allocation' wastage",
                       num(t->muw3), 
                       num(t->muw3size),
                       pc(t->muw3size, t->mufragsize));
    fprintf(f, ff, "'Unused chunks' wastage",
                       num(t->muw4), 
                       num(t->muw4size),
                       pc(t->muw4size, t->mufragsize));

    return NULL;
}


/*
 * Called from analyse_print_details(..) below to print the full pathname
 *  of an object.
 */

static void print_path_name (FILE *g, ForDPtr object)
{
    ForDPtr objs[MAXCOMPONENTS];
    int i = 0;

    do
    {
        objs[i] = object;
        i++;
        object = tree_get_parent (object);
    } while (object != NULL);

    fprintf(g, "$");
    i -= 2;
    while (i >= 0)
    {
        fprintf(g, ".%s", tree_get_name(objs[i]));
        i--;
    }

    return;
}


/*
 * Print details to a file.
 */

static error * analyse_print_details (FILE *g, void *h)
{
    ScanListPtr p = (ScanListPtr) h;
    DiscInfoPtr di = p->di;
    DiscRecordPtr dr = &di->dr;
    FragPtr *frag = p->frag;
    int fragid;

    fprintf(g, "Details of allocated fragments for %s::%d\n\n",
                   p->FS == FS_ADFS ? "ADFS" : "SCSI", p->drive);

    fprintf(g, "\
Addresses and sizes are given in sectors, and 'off' is the sector offset\n\
of the start of an object inside a shared fragment.\n\
\n\
Possible fragment types are:\n\
\n\
  SS - shared fragment\n\
  SU - 'single-chunk' unshared fragment\n\
  MU - 'multi-chunk' unshared fragment\n\
  UK - 'lost' fragment\n\
\n\
         fragment              object\n\
  id type  address  size  size off  name\n\
\n\
");

    for (fragid = 0; fragid < di->maxfrags; fragid++)
    {
        FragPtr f = frag[fragid];

        if (f != NULL)
        {
            Bool firsttime = TRUE;

            fprintf (g, "%5d ", fragid);
            switch (f->v.type)
            {
            case FTYPE_UK:
                fprintf (g, "UK: %8d/%5d", f->sector, f->v.len);
                {
                    ExtraFragPtr p = f->next;

                    while (p != NULL)
                    {
                        fprintf (g, "\n          %8d/%5d",
                                        p->sector, p->len);
                        p = p->next;
                    }
                }
                break;
            case FTYPE_SU:
                fprintf (g, "SU: %8d/%5d %5d       ", f->sector, f->v.len,
                         f->u.ford->v.isdir ?
                             SECS_FOR (di, 2048) :
                             SECS_FOR (di, f->u.ford->u.len) );
                print_path_name (g, f->u.ford);
                break;
            case FTYPE_MU:
                fprintf (g, "MU: %8d/%5d", f->sector, f->v.len);
                fprintf (g, " %5d       ",
                             f->u.ford->v.isdir ?
                                 SECS_FOR (di, 2048) :
                                 SECS_FOR (di, f->u.ford->u.len) );
                print_path_name (g, f->u.ford);
                {
                    ExtraFragPtr p = f->next;

                    while (p != NULL)
                    {
                        fprintf (g, "\n          %8d/%5d",
                                        p->sector, p->len);
                        p = p->next;
                    }
                }
                break;
            case FTYPE_SS:
                fprintf (g, "SS: %8d/%5d", f->sector, f->v.len);

                /* special case for the fragment containing the map */
                if (fragid == 2)
                {
                    ExtraFragPtr p = f->next;

                    while (p != NULL)
                    {
                        fprintf (g, "   (boot block)\n          %8d/%5d",
                                        p->sector, p->len);
                        p = p->next;
                    }
                }

                {
                    ForDListPtr p = f->u.fordlist;

                    while (p != NULL)
                    {
                        if (firsttime)
                            firsttime = FALSE;
                        else
                            fprintf (g, "\n                        ");
                        fprintf (g, " %5d/%4d  ",
                                 p->ford->v.isdir ?
                                     SECS_FOR (di, 2048) :
                                     SECS_FOR (di, p->ford->u.len),
                                 (p->ford->v.sector - 1) << dr->shareshift);
                        print_path_name (g, p->ford);
                        p = p->next;
                    }
                }
                break;
            }
            fprintf (g, "\n");
        }
    }

    return NULL;
}


/*
 * Called from the saveas_save(..) handler to write out the window's content
 *  to a given open file.
 */

error * analyse_print (FILE *f, IdBlock *idblock, void *p)
{
    ComponentId menuid = idblock->parent_component;

    return (menuid == ID_MENU_details) ? analyse_print_details (f, p)
                                       : analyse_print_summary (f, p);
}


/*
 * Called by the saveas handlers to discover where the filename for the
 *  data to be saved is stored.
 */

char * analyse_saveas_filename (IdBlock *idblock, void *p)
{
    ComponentId menuid = idblock->parent_component;

    IGNORE(p);

    return (menuid == ID_MENU_details) ? detailsfn : summaryfn;
}
