/* annotated-file.c:
 *
 * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
 ****************************************************************
 * Copyright (C) 2005 Canonical Limited
 *        Authors: Robert Collins <robert.collins@canonical.com>
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/bugs/panic.h"
#include "hackerlab/char/str.h"
#include "libarch/debug.h"
#include "libarch/file-offset-mapper.h"

void
file_offset_map_entry_init (file_offset_map_entry_t * entry, int from, int to, int length)
{
    entry->from = from;
    entry->to = to;
    entry->length = length;
}

void
file_offset_mapper_init (file_offset_mapper_t *mapper, int lines)
{
    file_offset_map_entry_t *entry;
    mapper->map = NULL;
    entry = ar_ref_file_offset_map_entry (&mapper->map, 0);
    file_offset_map_entry_init (entry, 0, 0, lines);
}

/**
 * \brief find the containing block 
 */
static int
find_from (file_offset_mapper_t *mapper, int line)
{
    int position;
    for (position = 0; position < ar_size_file_offset_map_entry (mapper->map); ++position)
        if (mapper->map[position].from <= line && mapper->map[position].from + mapper->map[position].length > line)
            return position;
    return -1;
}

#include "hackerlab/vu/safe-printfmt.h"
static void
file_offset_map_entry_print (int fd, file_offset_map_entry_t entry)
{
    safe_printfmt(2, "%d[-%d] %d[-%d] %d", entry.from, 
                  entry.from + entry.length - 1,
                  entry.to,
                  entry.to + entry.length - 1,
                  entry.length);
}

void
file_offset_mapper_print(int fd, file_offset_mapper_t *mapper)
{
    int position;
    for (position = 0; position < ar_size_file_offset_map_entry (mapper->map); ++position)
      {
        safe_printfmt (2, "%d: ", position);
        file_offset_map_entry_print (fd, mapper->map[position]);
        safe_printfmt (2, "\n");
      }
}

void
file_offset_mapper_subtract (file_offset_mapper_t *mapper, int line)
{
    int block_offset = find_from(mapper, line);
    file_offset_map_entry_t new_entry;
    debug(dbg_annotate, 8, "file_offset_mapper_subtract (&mapper, %d);\n", line);

    if (block_offset == -1)
      {
        int position;
        debug (dbg_annotate, 8, "file_offset_mapper_subtract non mapped line %d\n", line);
        block_offset = 0;
        while (block_offset < ar_size_file_offset_map_entry (mapper->map) &&
               line > mapper->map[block_offset].from)
            ++block_offset;
        /* start of entry */
        for (position = block_offset; position < ar_size_file_offset_map_entry (mapper->map); ++position)
            ++mapper->map[position].from;
        return;
      }

    if (mapper->map[block_offset].from == line)
      {
        int position;
        /* start of entry */
        for (position = block_offset; position < ar_size_file_offset_map_entry (mapper->map); ++position)
            ++mapper->map[position].from;
        
        return;
      }
    /* split the block */
      {
        int new_latter_length;
        int position;
        new_latter_length = line - mapper->map[block_offset].from;

        file_offset_map_entry_init (&new_entry, line, 
                                    mapper->map[block_offset].to + new_latter_length,
                                    mapper->map[block_offset].length - line + mapper->map[block_offset].from);
        ar_insert_file_offset_map_entry (&mapper->map, block_offset + 1, new_entry);
        mapper->map[block_offset].length = new_latter_length;
        /* adjust the new block and all successors up one */
        for (position = block_offset + 1; position < ar_size_file_offset_map_entry (mapper->map); ++position)
            ++mapper->map[position].from;
          //mapper->map[block_offset].length - mapper->map[block_offset + 1].length - 1;

//                                    line - mapper->map[block_offset].from);
      }
}

/**
 * \brief check if blocks block, block + 1 can be consolidated,
 * assumes block_offset is not the final offset.
 * \return non-zero if they can be consolidated.
 */
static int
file_offset_mapper_can_consolidate (file_offset_mapper_t *mapper, int block_offset)
{
    if (mapper->map[block_offset].from + mapper->map[block_offset].length != mapper->map[block_offset + 1].from)
        return 0;
    if (mapper->map[block_offset].to + mapper->map[block_offset].length != mapper->map[block_offset + 1].to)
        return 0;
    return -1;
}

/**
 * \brief consolidate two blocks.
 * assumes block_offset is not the final offset.
 */
static void
file_offset_mapper_consolidate (file_offset_mapper_t *mapper, int block_offset)
{
    if (!file_offset_mapper_can_consolidate (mapper, block_offset))
        return;
    mapper->map[block_offset].length += mapper->map[block_offset + 1].length;
    ar_remove_file_offset_map_entry (&mapper->map, block_offset + 1);
}


/* 
 * \brief
 * mark line as being set - its current target will be mapped an unreachable.
 * if line has no target (due to a prior subtract), then it gets given a new target
 * of the active target after it.
 * all existing source maps will map to the value the next up 
 * valid source mapped to.
 */
void
file_offset_mapper_add (file_offset_mapper_t *mapper, int line)
{
    file_offset_map_entry_t new_entry;
    int block_offset = find_from(mapper, line);
    debug(dbg_annotate, 8, "file_offset_mapper_add (&mapper, %d);\n", line);
    if (line < 0)
        return;
    if (block_offset == -1)
      {
        /* currently maps to deleted */
        /* 
         * i.e.
         * line 1
         * 0 - 0
         * 1 - deleted
         * 2 - deleted
         * 3 - 1
         * ->
         * 0 - 0
         * 1 - deleted
         * 2 - 1
         * : set block_offset->from -1. and block_offset one higher.
         * if there are no successive blocks, we do nothing.
         */
        block_offset = 0;
        while (block_offset < ar_size_file_offset_map_entry (mapper->map) &&
               line > mapper->map[block_offset].from)
            ++block_offset;
        if (block_offset == ar_size_file_offset_map_entry (mapper->map))
            return;
        mapper->map[block_offset].from -= 1;
        block_offset += 1;
      }
    while (block_offset != -1)
      {
        if (block_offset >= ar_size_file_offset_map_entry (mapper->map))
          {
            /* last block */
            debug(dbg_annotate, 8, "Done on block: %d\n", block_offset);
            block_offset = -1;
            break;
          }
        if (line == mapper->map[block_offset].from)
          {
            debug(dbg_annotate, 8, "Shrinking (first block): %d\n", block_offset);
            mapper->map[block_offset].length -= 1;
            mapper->map[block_offset].to += 1;
            if (!mapper->map[block_offset].length)
                ar_remove_file_offset_map_entry (&mapper->map, block_offset);
            else
                block_offset +=1;
          }
        else if (line > mapper->map[block_offset].from)
          {
            /* first block - split with no adjustment. */
            if (mapper->map[block_offset].length == 2)
              {
                debug(dbg_annotate, 8, "Shrinking (first block split): %d\n", block_offset);
                mapper->map[block_offset].length -= 1;
                if (!mapper->map[block_offset].length)
                    ar_remove_file_offset_map_entry (&mapper->map, block_offset);
                else
                    block_offset += 1;
                if (block_offset == ar_size_file_offset_map_entry (mapper->map))
                    block_offset = -1;
              }
            else
              {
                debug(dbg_annotate, 8, "Splitting (first block): %d\n", block_offset);
                file_offset_map_entry_init (&new_entry, 
                                            mapper->map[block_offset].from, 
                                            mapper->map[block_offset].to, 
                                            line - mapper->map[block_offset].from);
                ar_insert_file_offset_map_entry (&mapper->map, block_offset, new_entry);
                mapper->map[block_offset + 1].from = line;
                mapper->map[block_offset + 1].to = mapper->map[block_offset].to + mapper->map[block_offset].length + 1;
                mapper->map[block_offset + 1].length -= mapper->map[block_offset].length;
                mapper->map[block_offset + 1].length -= 1;
                if (!mapper->map[block_offset + 1].length)
                    ar_remove_file_offset_map_entry (&mapper->map, block_offset + 1);
                else
                    block_offset += 1;
                block_offset += 1;
              }
          }
        else
          {
            /* adjust current block. */
            if (mapper->map[block_offset].length == 1)
              {
                /* non contiguous, single length block */
                debug(dbg_annotate, 8, "1 line non contigous block: %d: %d->%d\n", block_offset, mapper->map[block_offset].from, mapper->map[block_offset].to);
                mapper->map[block_offset].from -=1;
                /* move to next block */
                block_offset += 1;
              }
            else
              {
                /* split this block */
                debug(dbg_annotate, 8, "lowering later block: %d\n", block_offset);
                mapper->map[block_offset].from -=1;
                block_offset += 1;
              }
          }      
      }

    /* note: if it ever matters, count down - more efficient */
    block_offset = 0;
    while (block_offset + 1 < ar_size_file_offset_map_entry (mapper->map))
      {
        if (file_offset_mapper_can_consolidate (mapper, block_offset))
            file_offset_mapper_consolidate (mapper, block_offset);
        else
            ++block_offset;
      }
    return;
}

int
file_offset_mapper_map (file_offset_mapper_t *mapper, int line)
{
    /* find line in the map */
    int block = find_from(mapper, line);
    int result;
    if (block == -1)
        return -1;
    result = mapper->map[block].to + line - mapper->map[block].from;
    if (block + 1 == ar_size_file_offset_map_entry (mapper->map))
        /* cannot have dups at the end */
        return result;
    
 //   if (mapper->map[block + 1].from == line)
 //       return mapper->map[block + 1].to;
    return result;
}

void
file_offset_mapper_finalise (file_offset_mapper_t *mapper)
{
    ar_free_file_offset_map_entry (&mapper->map);
}


syntax highlighted by Code2HTML, v. 0.9.1