//See ::/Doc/Credits.DD.
//See CArcCompress, CArcEntry, and CArcCtrl

asm {
/****
U0 ArcEntryGet(CArcCtrl *c)
{
  I64 i;
  CArcEntry *tmp,*tmp1;

  if (c->entry_used) {
    i=c->free_idx;

    c->entry_used=FALSE;
    c->cur_entry=c->next_entry;
    c->cur_bits_in_use=c->next_bits_in_use;
    if (c->next_bits_in_use<ARC_BITS_MAX) {
      c->next_entry = &c->compress[i++];
      if (i==c->free_limit) {
        c->next_bits_in_use++;
        c->free_limit=1<<c->next_bits_in_use;
      }
    } else {
      do if (++i==c->free_limit)
          i=c->min_table_entry;
      while (c->hash[i]);
      tmp=&c->compress[i];
      c->next_entry=tmp;
      tmp1=&c->hash[tmp->basecode];
      while (tmp1) {
        if (tmp1->next==tmp) {
          tmp1->next=tmp->next;
          break;
        } else
          tmp1=tmp1->next;
      }
    }
    c->free_idx=i;
  }
}
****/
_ARC_ENTRY_GET::
        PUSH    RBP
        MOV     RBP,RSP
        PUSH    RSI
        PUSH    RDI
        MOV     RSI,U64 SF_ARG1[RBP]
        BTR     U64 CArcCtrl.entry_used[RSI],0
        JNC     I32 @@30
        MOV     RDX,U64 CArcCtrl.free_idx[RSI]
        MOV     RAX,U64 CArcCtrl.next_entry[RSI]
        MOV     U64 CArcCtrl.cur_entry[RSI],RAX
        MOV     RCX,U64 CArcCtrl.next_bits_in_use[RSI]
        MOV     U64 CArcCtrl.cur_bits_in_use[RSI],RCX
        CMP     RCX,ARC_BITS_MAX
        JAE     @@05
        MOV     RAX,RDX
        SHL     RAX,4
        LEA     RAX,U64 CArcCtrl.compress[RSI+RAX]
        MOV     U64 CArcCtrl.next_entry[RSI],RAX
        INC     RDX
        CMP     U64 CArcCtrl.free_limit[RSI],RDX
        JNE     @@25
        INC     RCX
        MOV     U64 CArcCtrl.next_bits_in_use[RSI],RCX
        MOV     RAX,1
        SHL     RAX,CL
        MOV     U64 CArcCtrl.free_limit[RSI],RAX
        JMP     @@25
@@05:   INC     RDX
        CMP     U64 CArcCtrl.free_limit[RSI],RDX
        JNE     @@10
        MOV     RDX,U64 CArcCtrl.min_table_entry[RSI]
@@10:   MOV     RAX,U64 CArcCtrl.hash[RSI+RDX*8]
        TEST    RAX,RAX
        JNZ     @@05
        MOV     RDI,RDX
        SHL     RDI,4
        LEA     RDI,U64 CArcCtrl.compress[RSI+RDI]
        MOV     U32 CArcCtrl.next_entry[RSI],EDI
        MOVZX   RBX,U16 CArcEntry.basecode[RDI]
        LEA     RCX,U64 CArcCtrl.hash[RSI+RBX*8]
@@15:   TEST    RCX,RCX
        JZ      @@25
        MOV     RAX,U64 CArcEntry.next[RCX]
        CMP     RDI,RAX
        JNE     @@20
        MOV     RAX,U64 CArcEntry.next[RDI]
        MOV     U64 CArcEntry.next[RCX],RAX
        JMP     @@25
@@20:   MOV     RCX,RAX
        JMP     @@15
@@25:   MOV     U64 CArcCtrl.free_idx[RSI],RDX
@@30:   POP     RDI
        POP     RSI
        POP     RBP
        RET1    8
}

_extern _ARC_ENTRY_GET U0 ArcEntryGet(CArcCtrl *c);

I64 ArcDetermineCompressionType(U8 *src,I64 size)
{
  while (size--)
    if (*src++&0x80)
      return CT_8_BIT;
  return CT_7_BIT;
}

U0 ArcCompressBuf(CArcCtrl *c)
{//Use CompressBuf() unless doing more than one buf.
  CArcEntry *tmp,*tmp1;
  I64 ch,basecode;
  U8 *src_ptr,*src_limit;

  src_ptr=c->src_buf+c->src_pos;
  src_limit=c->src_buf+c->src_size;

  if (c->saved_basecode==U32_MAX)
    basecode=*src_ptr++;
  else
    basecode=c->saved_basecode;

  while (src_ptr<src_limit && c->dst_pos+c->cur_bits_in_use<=c->dst_size) {
    ArcEntryGet(c);
ac_start:
    if (src_ptr>=src_limit) goto ac_done;
    ch=*src_ptr++;
    if (tmp=c->hash[basecode])
      do {
        if (tmp->ch==ch) {
          basecode=tmp-&c->compress[0];
          goto ac_start;
        }
      } while (tmp=tmp->next);

    BFieldOrU32(c->dst_buf,c->dst_pos,basecode);
    c->dst_pos+=c->cur_bits_in_use;

    c->entry_used=TRUE;
    tmp=c->cur_entry;
    tmp->basecode=basecode;
    tmp->ch=ch;
    tmp1=&c->hash[basecode];
    tmp->next=tmp1->next;
    tmp1->next=tmp;

    basecode=ch;
  }
ac_done:
  c->saved_basecode=basecode;
  c->src_pos=src_ptr-c->src_buf;
}

Bool ArcFinishCompression(CArcCtrl *c)
{//Do closing touch on archivew ctrl struct.
  if (c->dst_pos+c->cur_bits_in_use<=c->dst_size) {
    BFieldOrU32(c->dst_buf,c->dst_pos,c->saved_basecode);
    c->dst_pos+=c->next_bits_in_use;
    return TRUE;
  } else
    return FALSE;
}

U0 ArcExpandBuf(CArcCtrl *c)
{//Use ExpandBuf() unless you know what you're doing.
  U8 *dst_ptr,*dst_limit;
  I64 basecode,lastcode,code;
  CArcEntry *tmp,*tmp1;

  dst_ptr=c->dst_buf+c->dst_pos;
  dst_limit=c->dst_buf+c->dst_size;

  while (dst_ptr<dst_limit && c->stk_ptr!=c->stk_base)
    *dst_ptr++ = * -- c->stk_ptr;

  if (c->stk_ptr==c->stk_base && dst_ptr<dst_limit) {
    if (c->saved_basecode==U32_MAX) {
      lastcode=BFieldExtU32(c->src_buf,c->src_pos,
            c->next_bits_in_use);
      c->src_pos+=c->next_bits_in_use;
      *dst_ptr++=lastcode;
      ArcEntryGet(c);
      c->last_ch=lastcode;
    } else
      lastcode=c->saved_basecode;
    while (dst_ptr<dst_limit && c->src_pos+c->next_bits_in_use<=c->src_size) {
      basecode=BFieldExtU32(c->src_buf,c->src_pos,
            c->next_bits_in_use);
      c->src_pos+=c->next_bits_in_use;
      if (c->cur_entry==&c->compress[basecode]) {
        *c->stk_ptr++=c->last_ch;
        code=lastcode;
      } else
        code=basecode;
      while (code>=c->min_table_entry) {
        *c->stk_ptr++=c->compress[code].ch;
        code=c->compress[code].basecode;
      }
      *c->stk_ptr++=code;
      c->last_ch=code;

      c->entry_used=TRUE;
      tmp=c->cur_entry;
      tmp->basecode=lastcode;
      tmp->ch=c->last_ch;
      tmp1=&c->hash[lastcode];
      tmp->next=tmp1->next;
      tmp1->next=tmp;

      ArcEntryGet(c);
      while (dst_ptr<dst_limit && c->stk_ptr!=c->stk_base)
        *dst_ptr++ = * -- c->stk_ptr;
      lastcode=basecode;
    }
    c->saved_basecode=lastcode;
  }
  c->dst_pos=dst_ptr-c->dst_buf;
}

CArcCtrl *ArcCtrlNew(Bool expand,I64 compression_type=CT_8_BIT)
{//MAlloc archive ctrl struct.
  CArcCtrl *c;
  c=CAlloc(sizeof(CArcCtrl));
  if (expand) {
    c->stk_base=MAlloc(1<<ARC_BITS_MAX);
    c->stk_ptr=c->stk_base;
  }
  if (compression_type==CT_7_BIT)
    c->min_bits=7;
  else
    c->min_bits=8;
  c->min_table_entry=1<<c->min_bits;
  c->free_idx=c->min_table_entry;
  c->next_bits_in_use=c->min_bits+1;
  c->free_limit=1<<c->next_bits_in_use;
  c->saved_basecode=U32_MAX;
  c->entry_used=TRUE;
  ArcEntryGet(c);
  c->entry_used=TRUE;
  return c;
}

U0 ArcCtrlDel(CArcCtrl *c)
{//Free archive ctrl struct.
  Free(c->stk_base);
  Free(c);
}

U8 *ExpandBuf(CArcCompress *arc,CTask *mem_task=NULL)
{//See ::/Demo/Dsk/SerializeTree.HC.
  CArcCtrl *c;
  U8 *res;

  if (!(CT_NONE<=arc->compression_type<=CT_8_BIT) ||
        arc->expanded_size>mem_mapped_space)
    throw('Compress');

  res=MAlloc(arc->expanded_size+1,mem_task);
  res[arc->expanded_size]=0; //terminate
  switch [arc->compression_type] {
    case CT_NONE:
      MemCpy(res,&arc->body,arc->expanded_size);
      break;
    case CT_7_BIT:
    case CT_8_BIT:
      c=ArcCtrlNew(TRUE,arc->compression_type);
      c->src_size=arc->compressed_size<<3;
      c->src_pos=sizeof(CArcCompress)<<3;
      c->src_buf=arc;
      c->dst_size=arc->expanded_size;
      c->dst_buf=res;
      c->dst_pos=0;
      ArcExpandBuf(c);
      ArcCtrlDel(c);
      break;
  }
  return res;
}

CArcCompress *CompressBuf(U8 *src,I64 size,CTask *mem_task=NULL)
{//See ::/Demo/Dsk/SerializeTree.HC.
  CArcCompress *arc;
  I64 size_out,compression_type=ArcDetermineCompressionType(src,size);
  CArcCtrl *c=ArcCtrlNew(FALSE,compression_type);
  c->src_size=size;
  c->src_buf=src;
  c->dst_size=(size+sizeof(CArcCompress))<<3;
  c->dst_buf=CAlloc(c->dst_size>>3);
  c->dst_pos=sizeof(CArcCompress)<<3;
  ArcCompressBuf(c);
  if (ArcFinishCompression(c) && c->src_pos==c->src_size) {
    size_out=(c->dst_pos+7)>>3;
    arc=MAlloc(size_out,mem_task);
    MemCpy(arc,c->dst_buf,size_out);
    arc->compression_type=compression_type;
    arc->compressed_size=size_out;
  } else {
    arc=MAlloc(size+sizeof(CArcCompress),mem_task);
    MemCpy(&arc->body,src,size);
    arc->compression_type=CT_NONE;
    arc->compressed_size=size+sizeof(CArcCompress);
  }
  arc->expanded_size=size;
  Free(c->dst_buf);
  ArcCtrlDel(c);
  return arc;
}