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

/*
** Local Prototypes
*/
char *seg_name( int, word * );
char *grp_name( int );
void get_seg( char *, FIX_T *, int, void *, int );
void assume( int, int, int );

FIXER_T fix_type[] = {
    1, "db",     "low ",      /* LOBYTE  */
    2, "dw",  "offset ",      /* OFFSET  */
    2, "dw",     "seg ",      /* BASE    */
    4, "dd",     "ptr ",      /* POINTER */
    1, "db",    "high ",      /* HIBYTE  */
    2, "dw",  "offset "  };   /* Loader-resolved */
                              /*     OFFSET      */

char *seg_name( index, size )
    int             index;
    word    *size;
{
    SEG_T   *segment_rec;
    NAME_T  *name_rec;

    seg_search.index = index;
    segment_rec = (SEG_T *)find( (char *)&seg_search, segment_tree, 
                                                        TC seg_compare, NULL );
    if ( segment_rec == NULL ) fmt_error( "Undefinded segment" );
    *size = segment_rec->length;
    name_search.index = segment_rec->name;
    name_rec = (NAME_T *)find( (char *)&name_search, name_tree, 
                                                        TC name_compare, NULL );
    if ( name_rec == NULL ) fmt_error( "Undefined segment name" );
    return( name_rec->name );
}

char *grp_name( index )
    int     index;
{
    GRP_T   *grp_rec;
    NAME_T  *name_rec;

    grp_search.index = index;
    grp_rec = (GRP_T *)find( (char *)&grp_search, group_tree, 
                                                        TC grp_compare, NULL );
    if ( grp_rec == NULL ) fmt_error( "Undefined group" );
    name_search.index = grp_rec->name;
    name_rec = (NAME_T *)find( (char *)&name_search, name_tree, 
                                                        TC name_compare, NULL );
    if ( name_rec == NULL ) fmt_error( "Undefined group name" );
    return( name_rec->name );
}

char *mode_name( mode, index )
    int             mode;
    int             index;
{
    word    dummy;

    if ( index == 0 ) {
        return( "NOTHING" );
    }

    switch( mode ) {
        case SEGMENT:   return( seg_name(index,&dummy) );
        case GROUP:     return( grp_name(index) );
        case LOCATION:  return( cseg_name );

        case EXTERNAL:
        case FRAME:
        case TARGET:
        case NONE:      return( NULL );
    }
    return(NULL);
}

void assume( seg_reg, data_mode, data_index )
    int             seg_reg;
    int             data_mode;
    int             data_index;
{
    char            *seg_name;
    extern  char    *sregsc[];
    char            operand[100];

    seg_name = mode_name( data_mode, data_index );
    if (seg_name && compatibility != 2 && pass == 3 ) {
        out_opcode( "assume" );
        sprintf( operand, "%s %s", sregsc[seg_reg], seg_name );
        out_operand( operand );
        out_endline();
    }
}

void adjust_assumes()
{
    int     seg_reg;
    int     new_mode;
    int     new_index;

    for ( seg_reg = 0; seg_reg < MAX_SEG_REGS; seg_reg++ ) {
        new_mode  = seg_rec->new_mode [seg_reg];
        new_index = seg_rec->new_index[seg_reg];
        if ( new_mode  != seg_rec->prv_mode [seg_reg] ||
             new_index != seg_rec->prv_index[seg_reg] ) {
            assume( seg_reg, new_mode, new_index );
            seg_rec->prv_mode [seg_reg] = new_mode;
            seg_rec->prv_index[seg_reg] = new_index;
        }
    }
}

void abort_assumes()
{
    int     seg_reg;

    for ( seg_reg = 0; seg_reg < MAX_SEG_REGS; seg_reg++ ) {
        seg_rec->new_mode [seg_reg] = seg_rec->prv_mode [seg_reg];
        seg_rec->new_index[seg_reg] = seg_rec->prv_index[seg_reg];
    }
}

void get_seg( result, fixup, ref_mode, data, seg_reg )
    char    *result;
    FIX_T   *fixup;
    int     ref_mode;
    void    *data;
    int     seg_reg;
{
    char    *this_name;
    int     form;
    int     mode;
    int     index;
    int     ref_seg;
    EXT_T   *ext_rec;
    PUB_T   *pub_rec;

    form  = fixup->form;
    mode  = fixup->a_mode;
    index = fixup->a_index;

    strcpy( result, "" );

    if ( mode == TARGET || mode == NONE ) {
        return;
    }

    /*
    ** Check for Externals with implied or lacking segment
    **  This means that the fixup tells that the external was defined in
    **  that segment (for implied, SI:EI). Or that the external was defined
    **  outside any segment (for lacking, EI:EI).
    */
    if ( fixup->b_mode == EXTERNAL ) {
        ext_rec = data;
        if ( mode == EXTERNAL ) {
            /* Must be EI:EI */
            ext_rec->used = -1;         /* Remove from any segments! */
            return;
        }
        if ( mode == SEGMENT ) {
            ext_rec->pos_abs = FALSE;
            if ( ref_mode != 0 ) {
                /* Must be Segment:EI */
                if ( pass == 1 && ext_rec->used == 0 ) {
                    sex_insert( index, ext_rec );
                    ext_rec->used = index;
                }
                if ( ext_rec->used == index ) {
                    return;
                }
            }
        }
    } else {
        if ( fixup->b_mode == SEGMENT ) {
            pub_rec = data;
            if ( ref_mode == 2 && (ref_mode == 1 && form == POINTER) ) {
                if ( pub_rec->seg_idx == index ) {
                    return;
                }
            }
        }
    }

    /*
    ** On first important fixup, assign the "ASSIGN xx:" values
    */
    if ( ref_mode == 0 || (ref_mode == 1 && form != POINTER) ) {
        ref_seg = seg_reg - ES;                 /* Convert to 0,1,2,3,etc */
        seg_rec->new_mode [ref_seg] = mode;
        seg_rec->new_index[ref_seg] = index;
        if ( mode == SEGMENT ) {
            if ( fixup->b_mode == EXTERNAL && ext_rec->used == index ) {
                return;
            }
        }
        if ( mode == SEGMENT || mode == GROUP ) {
            if ( fixup->b_mode == SEGMENT || fixup->b_mode == EXTERNAL ) {
                return;
            }
        }
        /*
        ** Frame on a code reference is always taken from the assume?
        */
    }

    if ( form == BASE ) {
        if ( fixup->b_mode == mode && fixup->b_index == index ) {
            return;
        }
    }

    this_name = mode_name( mode, index );
    if ( this_name ) {
        sprintf( result, "%s:", this_name );
        return;
    } else {
        return;
    }
}

PUB_T *check_public( mode, index, displacement, label_char )
    int             mode;
    int             index;
    long            displacement;
    char            label_char;
{
    PUB_T           *new_pub_rec;
    NODE_T          *new_node;
    DAT_T           dat_search;
    DAT_T           *dat_rec;
    NODE_T          *dat_node;
    FIX_T           *fix_rec;
    NODE_T          *fix_node;
    int             ref_seg;
    char            label_name[10];
    dword   disp;
    int             fix_count;

#ifdef DEBUG
printf("--> check_public for [%04X:%04lX]\n", index, displacement );
#endif

    if ( over_seg == -1 ) {
        ref_seg = 2;
    } else {
        ref_seg = over_seg;
    }

    if ( displacement < 0L ) {
        disp = 0L;
    } else {
        disp = displacement;
    }

    if ( mode == 2 ) {
        index = seg_rec->prv_index[ref_seg];
    }

    pub_search.seg_idx = index;
    pub_search.offset  = disp;

    new_pub_rec = (PUB_T *)find( (char *)&pub_search, public_tree,
                                                    TC pub_compare, &new_node );
    if ( new_pub_rec ) {
        return( new_pub_rec );
    }
    /*
    ** If the address is just slightly past another public symbol, and
    ** the address is less than the public symbols address + its size,
    ** then it is probably a "public+x" type fixup.
    */
    if ( new_node != public_tree ) {
        new_pub_rec = (PUB_T *)new_node->data;
        if ( new_pub_rec->seg_idx == index ) {
            if ( new_pub_rec->offset + type_to_size(new_pub_rec->type) > disp ) {
                return( new_pub_rec );
            }
        }
    }
    /*
    ** Find the data record that that symbol probably belongs in
    */
    dat_search.seg_idx = index;
    dat_search.offset = disp;
    dat_rec = (DAT_T *)find( (char *)&dat_search, data_tree, 
                                                    TC dat_compare, &dat_node );
    if ( dat_node && dat_node != data_tree ) {
        dat_rec = (DAT_T *)dat_node->data;
        if ( dat_rec->seg_idx == index 
             && dat_rec->offset+dat_rec->length > disp ) {
            /*
            ** Make sure the symbol is not in the middle of a fixup
            */
            fix_search.seg_idx    = index;
            fix_search.dat_offset = dat_rec->offset;
            fix_search.offset     = (int)(disp - (dword)dat_rec->offset);
            fix_rec = (FIX_T *)find( (char *)&fix_search, fix_tree,
                                                      TC fix_compare, &fix_node );
            if ( !fix_rec ) {
                if ( fix_node != fix_tree ) {
                    fix_rec = (FIX_T *)fix_node->data;
                    if ( fix_rec->seg_idx == index
                         && fix_rec->dat_offset == dat_rec->offset ) {

                        fix_count = fix_type[fix_rec->form].num_bytes;

                        /*
                        ** BASE types do not get larger
                        */
                        if ( fix_rec->extended && fix_rec->form != BASE ) {
                            fix_count += 2;
                        }
                        if ( fix_search.offset > fix_rec->offset
                          && fix_search.offset < fix_rec->offset+fix_count ) {
                            return( new_pub_rec );
                        }
                    }
                }
            }
            /*
            ** Make sure that public is not being placed inside an
            ** iterated data block.  If it is, then retreat to the
            ** start of the iterated data block.  In this way, labels
            ** will never be generated inside iterated data blocks.
            */
            if ( dat_rec->type == ITERATED ) {
                if ( new_node == public_tree ) {
                    return( NULL );
                }
                if ( new_pub_rec->seg_idx != index ) {
                    return( NULL );
                }
                if ( new_pub_rec->offset >= dat_rec->offset ) {
                    return( new_pub_rec );
                }
                disp = dat_rec->offset;
            }
        }
    }

    /*
    ** Create a local label (only happens on 1st pass!)
    */
    label_count++;
    if ( compatibility == 2 ) {
        sprintf( label_name, "@%c%d", label_char, label_count );
    } else {
        sprintf( label_name, "$%c%d", label_char, label_count );
    }
#ifdef DEBUG
printf("Creating a new symbol <%s> at %d:%04X\n", label_name, index, disp);
#endif

    new_node = pub_insert( index, disp, label_name, LOCAL, FALSE );

    new_pub_rec = (PUB_T *)find( (char *)&pub_search, public_tree,
                                                        TC pub_compare, NULL );
    if ( (pub_node == public_tree || index < pub_rec->seg_idx || 
          (index == pub_rec->seg_idx && disp < pub_rec->offset)) &&
           (index > segment || (index == segment && disp > inst_offset)) ) {
        pub_node = new_node;
        pub_rec  = new_pub_rec;
    }
    if ( index == segment && disp == inst_offset ) {
        last_pub_rec = new_pub_rec;
    }

    return( new_pub_rec );
}

int find_member( text, structure, offset )
    char    *text;
    STRUC_T *structure;
    long    *offset;
{
    int     type;
    int     mem_cnt;
    long    dup_cnt;
    long     this_size;
    char    *cp;
    char    ch;

    mem_cnt = 0;
    cp = structure->form;

    while ( (ch=*cp) != '\0' ) {
        switch( ch ) {
            case '(':
                dup_cnt = atol( cp+1 );
                break;
            case ',':
                type = atoi( cp+1 );
                break;
            case ')':
                this_size = type_to_size(type)*dup_cnt;
                if ( *offset < this_size ) {
                    sprintf( text, ".s%dm_%d", structure->index, mem_cnt );
                    return( type );
                }
                *offset -= this_size;
                mem_cnt++;
            default:
                break;
        }
        cp++;
    }

    strcpy( text, ".past struct" );

    return( UNKNOWN );
}

void get_target( result, fixup, mode, data, pre_name, type, assign,
                                                        type_known, seg_reg )
    char        *result;        /* Output string                            */
    FIX_T       *fixup;         /* Fixup to figure out                      */
    int         mode;           /* 0 = Data Deref, 1 = Code, 2 = Data Ref   */
    long        data;           /* Value added (fixups get added onto this) */
    int         pre_name;       /* Are the fixup type words needed?         */
    int         type;           /* type of value pointed to (word ptr, etc) */
    int         assign;         /* Assign type if needed?                   */
    int         *type_known;    /* Is the "word ptr" text needed? (result)  */
    int         seg_reg;        /* Segment register used                    */
{
    int         index;
    PUB_T       *pub_rec;
    EXT_T       ext_search;
    EXT_T       *ext_rec;
    STRUC_T     *pub_struct;
    char        *prefix;
    char        this_seg[NAMESIZE+1+1];     /* 1 for ':', 1 for \0 */
    char        this_member[NAMESIZE+1+1];  /* 1 for ., 1 for \0 */
    word    seg_size;
    char        *this_name;
    char        *this_comment;
    int         this_type;
    char        sign;
    long        disp;

#ifdef DEBUG
printf("-> get_target for [%04X:%04X]\n", fix_rec->seg_idx, fix_rec->dat_offset );
#endif

    disp = data;

    this_seg[0] = '\0';
    this_member[0] = '\0';
    this_comment = "";

    /*
    ** Fixups cannot have a displacement value (stored with the fixup)
    ** AND a value added (stored in the image to which the fixup is added)
    */
    if ( fixup->displacement != 0L ) {
        if ( disp != 0L ) {
            this_comment = "[MULTIPLE FIXUP]";
        }
        disp += fixup->displacement;
    }

    if ( pre_name ) {
        prefix = fix_type[fixup->form].prefix;
    } else {
        prefix = "";
    }

    index = fixup->b_index;

    switch( fixup->b_mode ) {
        case SEGMENT:           /* Segment Index and Displacement */
            if ( fixup->form != BASE ) {
                /* PUBLIC names will be stored as segment + offset */
                pub_rec = check_public( 0, index, disp, 'S' );
                if ( pub_rec ) {
                    this_name = pub_rec->name;
                    disp -= (long)pub_rec->offset;
                    pub_struct = pub_rec->structure;
                    if ( pub_struct ) {
                        this_type = find_member( this_member, pub_struct,
                                                                      &disp );
                    } else {
                        if ( assign && ((type <= FAR && pub_rec->type > FAR)
                                || pub_rec->type == UNKNOWN) ) {
                            pub_rec->type = type;
                        }
                        this_type = pub_rec->type;
                    }
                    if ( this_type != type ) {
                        *type_known = FALSE;
                    } else {
                        *type_known = TRUE;
                    }
                    get_seg( this_seg, fixup, mode, pub_rec, seg_reg );
                    break;
                }
            }

            this_name = seg_name(index,&seg_size);
            if ( fixup->form == BASE && fixup->displacement != 0 ) {
                prefix = "";        /* MASM generates seg length, if */
                disp -= seg_size;   /* "SEG" is missing, do the same */
            }
            this_seg[0] = '\0';
            *type_known = TRUE;
            break;
        case GROUP:                     /* Group Index and Displacement */
            this_name = grp_name(index);
            *type_known = TRUE;
            get_seg( this_seg, fixup, mode, NULL, seg_reg );
            break;
        case EXTERNAL:                  /* External Index and Displacement */
            ext_search.index = index;
            ext_rec = (EXT_T *)find( (char *)&ext_search,
                                        extern_tree, TC ext_compare, NULL );
            if ( ext_rec == NULL ) fmt_error( "Undefined external" );

            this_name = ext_rec->name;

            if ( assign && ((type <= FAR && ext_rec->type > FAR)
                            || ext_rec->type == UNKNOWN) ) {
                ext_rec->type = type;
            }
            if ( ext_rec->type != type ) {
                *type_known = FALSE;
            } else {
                *type_known = TRUE;
            }
            get_seg( this_seg, fixup, mode, ext_rec, seg_reg );
            break;
        case FRAME:                     /* Frame Number and Displacement */
            get_seg( this_seg, fixup, mode, NULL, seg_reg );
            break;
    }

    if ( disp == 0 ) {
/* ;* */ sprintf( result, "%s%s%s%s%s", prefix, this_seg,
                     this_name, this_member, this_comment );
    } else {
        if ( disp > 0L ) {
            sign = '+';
        } else {
            sign = '-';
            disp = -disp;
        }
        if ( disp > 32767L || disp < -32768L ) {
            sprintf( result, "%s%s%s%s %c 0%08lXh%s",
                                prefix, this_seg,
                                this_name, this_member,
                                sign, disp,
                                this_comment );
        } else {
            sprintf( result, "%s%s%s%s %c 0%04lXh%s",
                                prefix, this_seg,
                                this_name, this_member,
                                sign, disp,
                                this_comment );
        }
    }
#ifdef DEBUG
printf("<- get_target [%04X:%04X]\n", fix_rec->seg_idx, fix_rec->dat_offset );
#endif

}

int get_fix( result, mode, pre_name, size_bytes, size, assign,
                                                    size_known, seg_reg )
    char            *result;        /* Resulting name will go here            */
    int             mode;           /* 0 = Data Deref, 1 = Code, 2 = Data Ref */
    int             pre_name;       /* Fixup type words needed?               */
    int             size_bytes;     /* Word size (386=4 others=2)             */
    int             size;           /* Size of expected value                 */
    int             assign;         /* TRUE to set size, FALSE to ignore      */
    int             *size_known;    /* Is "word ptr" text needed?             */
    int             seg_reg;        /* Segment register used                  */
{
    dword   data;
    dword   more;
    int             fix_count;

    size_bytes = size_bytes;
    fix_count = fix_type[fix_rec->form].num_bytes;

    /*
    ** BASE types do not get larger
    */
    if ( fix_rec->extended && fix_rec->form != BASE ) {
        fix_count += 2;
    }

    switch( fix_count ) {
        case 1: data = buff_getc();
                break;
        case 2: data = buff_getc();
                data += (buff_getc() << 8);
                break;
        case 4: data = buff_getc();
                data += (buff_getc() << 8);
                data += (buff_getc() << 16);
                data += (buff_getc() << 24);
                break;
        case 6: data = buff_getc();
                data += (buff_getc() << 8);
                data += (buff_getc() << 16);
                data += (buff_getc() << 24);
                more = buff_getc();
                more += (buff_getc() << 8);
                break;
        default:
                fmt_error( "Invalid fixup type" );
                break;
    }

    get_target( result, fix_rec, mode, data, pre_name, size, assign,
                                                        size_known, seg_reg );
    fix_advance();

    return( fix_count );
}
