//-----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation.  All Rights Reserved.
//
//-----------------------------------------------------------------------------
using System;
using System.IO;

namespace Microsoft.Cci.Pdb {
  internal class DataStream {
    internal DataStream() {
      this.contentSize = 0;
      this.pages = null;
    }

    internal DataStream(int contentSize, BitAccess bits, int count) {
      this.contentSize = contentSize;
      if (count > 0) {
        this.pages = new int[count];
        bits.ReadInt32(this.pages);
      }
    }

    internal void Read(PdbReader reader, BitAccess bits) {
      bits.MinCapacity(contentSize);
      Read(reader, 0, bits.Buffer, 0, contentSize);
    }

    internal void Read(PdbReader reader, int position,
                     byte[] bytes, int offset, int data) {
      if (position + data > contentSize) {
        throw new PdbException("DataStream can't read off end of stream. " +
                                       "(pos={0},siz={1})",
                               position, data);
      }
      if (position == contentSize) {
        return;
      }

      int left = data;
      int page = position / reader.pageSize;
      int rema = position % reader.pageSize;

      // First get remained of first page.
      if (rema != 0) {
        int todo = reader.pageSize - rema;
        if (todo > left) {
          todo = left;
        }

        reader.Seek(pages[page], rema);
        reader.Read(bytes, offset, todo);

        offset += todo;
        left -= todo;
        page++;
      }

      // Now get the remaining pages.
      while (left > 0) {
        int todo = reader.pageSize;
        if (todo > left) {
          todo = left;
        }

        reader.Seek(pages[page], 0);
        reader.Read(bytes, offset, todo);

        offset += todo;
        left -= todo;
        page++;
      }
    }

    internal void Write(PdbWriter writer, byte[] bytes) {
      Write(writer, bytes, bytes.Length);
    }

    internal void Write(PdbWriter writer, byte[] bytes, int data) {
      if (bytes == null || data == 0) {
        return;
      }

      int left = data;
      int used = 0;
      int rema = contentSize % writer.pageSize;
      if (rema != 0) {
        int todo = writer.pageSize - rema;
        if (todo > left) {
          todo = left;
        }

        int lastPage = pages[pages.Length - 1];
        writer.Seek(lastPage, rema);
        writer.Write(bytes, used, todo);
        used += todo;
        left -= todo;
      }

      if (left > 0) {
        int count = (left + writer.pageSize - 1) / writer.pageSize;
        int page0 = writer.AllocatePages(count);

        writer.Seek(page0, 0);
        writer.Write(bytes, used, left);

        AddPages(page0, count);
      }

      contentSize += data;
    }

    private void AddPages(int page0, int count) {
      if (pages == null) {
        pages = new int[count];
        for (int i = 0; i < count; i++) {
          pages[i] = page0 + i;
        }
      } else {
        int[] old = pages;
        int used = old.Length;

        pages = new int[used + count];
        Array.Copy(old, pages, used);
        for (int i = 0; i < count; i++) {
          pages[used + i] = page0 + i;
        }
      }
    }

    internal int Pages {
      get { return pages == null ? 0 : pages.Length; }
    }

    internal int Length {
      get { return contentSize; }
    }

    internal int GetPage(int index) {
      return pages[index];
    }

    internal int contentSize;
    internal int[] pages;
  }
}