#help_index "Cmd Line (Typically)"

#define DS_USE_FILE1    0
#define DS_USE_FILE2    1
#define DS_REMAINDER_1  2
#define DS_REMAINDER_2  3
#define DS_ABORT_FILE   4

I64 PopUpDiffMenu()
{
  I64 i;
  CDoc *doc=DocNew;
  DocPrint(doc,"$CM+LX,2,4$$FG$$BT,\"USE FILE1\",LE=DS_USE_FILE1$"
        "$CM+LX,24,0$$CYAN$$BT,\"USE FILE2\",LE=DS_USE_FILE2$"
        "$CM+LX,2,4$$FG$$BT,\"REMAINDER ALL FILE1\",LE=DS_REMAINDER_1$"
        "$CM+LX,24,0$$CYAN$$BT,\"REMAINDER ALL FILE2\",LE=DS_REMAINDER_2$"
        "$CM+LX,2,4$$FG$$BT,\"ABORT FILE\",LE=DS_ABORT_FILE$"
        "$CM+LX,24,0$$FG$$BT,\"ABORT ALL FILES\",LE=DOCM_CANCEL$\n");
  i=PopUpMenu(doc);
  DocDel(doc);
  return i;
}

I64 DiffEntriesCompare(CDocEntry *doc_e1,CDocEntry *doc_e2)
{
  return StrCmp(doc_e1->tag,doc_e2->tag);
}

#define DF_MODIFIED                     0x01
#define DF_DONT_MODIFIED                0x02
#define DF_REMAINDER_ALL_FILE1          0x04
#define DF_REMAINDER_ALL_FILE2          0x08
#define DF_ABORT_FILE                   0x10
#define DF_ABORT_ALL_FILES              0x20
#define DF_NO_MORE_PMTS_THIS_FILE       0x40

U0 DiffSel(CDoc *doc,I64 *_df_flags,I64 j1_lo,I64 j1_hi,
        I64 j2_lo,I64 j2_hi,I64 cnt1,I64 cnt2,
        CDocEntry **doc_unsorted1,CDocEntry **doc_unsorted2)
{
  CDocEntry *doc_e,*doc_e1,*doc_e2;
  Bool use_file1;
  I64 i,old_flags;
  CDoc *cur_l;
  if (!(*_df_flags & (DF_ABORT_FILE|DF_ABORT_ALL_FILES))) {
    "$RED$";
    if (0<=j1_lo<cnt1)
      "%d,",doc_unsorted1[j1_lo]->y+1;
    else if (0<=j1_hi-1<cnt1)
      "%d,",doc_unsorted1[j1_hi-1]->y+1;
    else
      "***,";
    if (0<=j2_lo<cnt2)
      "%d",doc_unsorted2[j2_lo]->y+1;
    else if (0<=j2_hi-1<cnt2)
      "%d",doc_unsorted2[j2_hi-1]->y+1;
    else
      "***";
    "---------------------$FG$\n";
    if (j1_lo<=0)
      i=0;
    else
      i=j1_lo-1;
    while (i<j1_hi) {
      if (cur_l=DocPut) {
        old_flags=cur_l->flags&DOCF_PLAIN_TEXT;
        cur_l->flags|=DOCF_PLAIN_TEXT;
      }
      "%s",doc_unsorted1[i++]->tag;
      if (cur_l)
        cur_l->flags= cur_l->flags&~DOCF_PLAIN_TEXT |old_flags;
      '\n';
    }
    "$CYAN$";
    if (j2_lo<=0)
      i=0;
    else
      i=j2_lo-1;
    while (i<j2_hi) {
      if (cur_l=DocPut) {
        old_flags=cur_l->flags&DOCF_PLAIN_TEXT;
        cur_l->flags|=DOCF_PLAIN_TEXT;
      }
      "%s",doc_unsorted2[i++]->tag;
      if (cur_l)
        cur_l->flags= cur_l->flags&~DOCF_PLAIN_TEXT |old_flags;
      '\n';
    }
    "$FG$";

    use_file1=TRUE;
    if (!(*_df_flags & DF_NO_MORE_PMTS_THIS_FILE)) {
      switch (PopUpDiffMenu) {
        case DS_USE_FILE1:
          break;
        case DS_USE_FILE2:
          use_file1=FALSE;
          break;
        case DS_REMAINDER_1:
          *_df_flags=*_df_flags&~DF_REMAINDER_ALL_FILE2|
                DF_REMAINDER_ALL_FILE1|DF_NO_MORE_PMTS_THIS_FILE;
          break;
        case DS_REMAINDER_2:
          *_df_flags=*_df_flags&~DF_REMAINDER_ALL_FILE1|
                DF_REMAINDER_ALL_FILE2|DF_NO_MORE_PMTS_THIS_FILE;
          break;
        case DS_ABORT_FILE:
          *_df_flags|=DF_DONT_MODIFIED|DF_ABORT_FILE|
                DF_NO_MORE_PMTS_THIS_FILE;
          break;
        default:
          *_df_flags|=DF_DONT_MODIFIED|DF_ABORT_ALL_FILES|
                DF_NO_MORE_PMTS_THIS_FILE;
      }
    }
    if (*_df_flags & DF_REMAINDER_ALL_FILE2 &&
          !(*_df_flags & (DF_DONT_MODIFIED|DF_REMAINDER_ALL_FILE1)))
      use_file1=FALSE;
    if (!use_file1) {
      *_df_flags|=DF_MODIFIED;
      doc_e1=doc_unsorted1[j1_lo]->last;
      if (j1_lo<j1_hi) {
        doc_e=doc_unsorted1[j1_lo];
        while (doc_e!=doc_unsorted1[j1_hi]) {
          doc_e2=doc_e->next;
          DocEntryDel(doc,doc_e);
          doc_e=doc_e2;
        }
      }
      if (j2_lo<j2_hi) {
        doc_e=doc_unsorted2[j2_lo];
        while (doc_e!=doc_unsorted2[j2_hi]) {
          doc_e2=DocEntryCopy(doc,doc_e);
          QueIns(doc_e2,doc_e1);
          doc_e1=doc_e2;
          doc_e=doc_e->next;
        }
      }
    }
  }
}

Bool DiffSub(CDoc *doc,I64 *_df_flags,I64 j1_lo,I64 j1_hi,I64 j2_lo,I64 j2_hi,
        I64 cnt1,I64 cnt2,CDocEntry **doc_sorted1,CDocEntry **doc_sorted2,
        CDocEntry **doc_unsorted1,CDocEntry **doc_unsorted2)
{
  I64 i,i1=0,i2=0,i2b,j1,j2,n,
        best_j1,best_j2,best_score=0,score;
  Bool res=FALSE;

  if (j1_lo>=j1_hi || j2_lo>=j2_hi) {
    if (j1_lo<j1_hi || j2_lo<j2_hi) {
      DiffSel(doc,_df_flags,j1_lo,j1_hi,j2_lo,j2_hi,cnt1,cnt2,
            doc_unsorted1,doc_unsorted2);
      return TRUE;
    } else
      return FALSE;
  }

  //Locate longest matching str in intervals
  while (i1<cnt1 && i2<cnt2) {
    if (!(j1_lo<=doc_sorted1[i1]->user_data<j1_hi)) //user_data is the new y
      i1++;
    else if (!(j2_lo<=doc_sorted2[i2]->user_data<j2_hi)) //user_data is new y
      i2++;
    else {
      i=StrCmp(doc_sorted1[i1]->tag,doc_sorted2[i2]->tag);
      if (i>0)
        i2++;
      else if (i<0)
        i1++;
      else {
        i2b=i2;
        while (!StrCmp(doc_sorted1[i1]->tag,doc_sorted2[i2]->tag)) {
          if (j2_lo<=doc_sorted2[i2]->user_data<j2_hi) {//user_data is the new y
            score=0;
            j1=doc_sorted1[i1]->user_data; //user_data is the new y
            j2=doc_sorted2[i2]->user_data; //user_data is the new y
            n=j1_hi-j1;
            if (j2_hi-j2<n)
              n=j2_hi-j2;
            while (score<n) {
              if (!StrCmp(doc_unsorted1[j1+score]->tag,
                    doc_unsorted2[j2+score]->tag))
                score++;
              else
                break;
            }
            if (score>best_score) {
              best_score=score;
              best_j1=j1;
              best_j2=j2;
            }
          }
          i2++;
          if (i2>=cnt2)
            break;
        }
        i2=i2b;
        i1++;
      }
    }
  }
  if (!best_score) {
    DiffSel(doc,_df_flags,j1_lo,j1_hi,j2_lo,j2_hi,cnt1,cnt2,
          doc_unsorted1,doc_unsorted2);
    return TRUE;
  } else {
    if (DiffSub(doc,_df_flags,j1_lo,best_j1,j2_lo,best_j2,cnt1,cnt2,
          doc_sorted1,doc_sorted2,doc_unsorted1,doc_unsorted2))
      res=TRUE;
    if (DiffSub(doc,_df_flags,best_j1+best_score,j1_hi,best_j2+best_score,
          j2_hi,cnt1,cnt2,
          doc_sorted1,doc_sorted2,doc_unsorted1,doc_unsorted2))
      res=TRUE;
    return res;
  }
}

Bool DiffBins(CDoc *doc1,CDoc *doc2)
{
  CDocBin *tmpb1=doc1->bin_head.next,
        *tmpb2=doc2->bin_head.next;

  if (tmpb1->last->last->num!=tmpb2->last->last->num)
    return TRUE;
  while (tmpb1!=&doc1->bin_head) {
    if (tmpb1->size!=tmpb2->size ||
          MemCmp(tmpb1->data,tmpb2->data,tmpb1->size))
      return TRUE;
    tmpb1=tmpb1->next;
    tmpb2=tmpb2->next;
  }
  return FALSE;
}

public Bool Diff(U8 *dst_file,U8 *src_file,I64 *_df_flags=NULL)
{//Report differences between two files and merge differences
//from src_file to dst_file.  Don't use _df_flags arg. (Used by Merge().)
  CDoc *doc1=DocRead(dst_file,DOCF_PLAIN_TEXT_TABS|DOCF_NO_CURSOR),
        *doc2=DocRead(src_file,DOCF_PLAIN_TEXT_TABS|DOCF_NO_CURSOR);
  CDocEntry *doc_e,**doc_sorted1,**doc_sorted2,**doc_unsorted1,**doc_unsorted2;
  I64 i,cnt1=0,cnt2=0,df_flags;
  Bool res=FALSE;

  if (_df_flags)
    df_flags=*_df_flags;
  else
    df_flags=0;
  df_flags&=DF_ABORT_ALL_FILES;

  doc_e=doc1->head.next;
  while (doc_e!=doc1) {
    if (doc_e->type_u8==DOCT_TEXT)
      doc_e->user_data=cnt1++; //user_data is the new y
    doc_e=doc_e->next;
  }

  doc_e=doc2->head.next;
  while (doc_e!=doc2) {
    if (doc_e->type_u8==DOCT_TEXT)
      doc_e->user_data=cnt2++; //user_data is the new y
    doc_e=doc_e->next;
  }

  doc_sorted1=MAlloc(cnt1*sizeof(CDocEntry *));
  doc_unsorted1=MAlloc((cnt1+1)*sizeof(CDocEntry *));
  i=0;
  doc_e=doc1->head.next;
  while (doc_e!=doc1) {
    if (doc_e->type_u8==DOCT_TEXT) {
      doc_sorted1[i]=doc_e;
      doc_unsorted1[i++]=doc_e;
    }
    doc_e=doc_e->next;
  }
  doc_unsorted1[i]=doc1;
  QSortI64(doc_sorted1,cnt1,&DiffEntriesCompare);

  doc_sorted2=MAlloc(cnt2*sizeof(CDocEntry *));
  doc_unsorted2=MAlloc((cnt2+1)*sizeof(CDocEntry *));
  i=0;
  doc_e=doc2->head.next;
  while (doc_e!=doc2) {
    if (doc_e->type_u8==DOCT_TEXT) {
      doc_sorted2[i]=doc_e;
      doc_unsorted2[i++]=doc_e;
    }
    doc_e=doc_e->next;
  }
  doc_unsorted2[i]=doc2;
  QSortI64(doc_sorted2,cnt2,&DiffEntriesCompare);

  res=DiffSub(doc1,&df_flags,0,cnt1,0,cnt2,cnt1,cnt2,
        doc_sorted1,doc_sorted2,doc_unsorted1,doc_unsorted2);
  if (df_flags&DF_MODIFIED && !(df_flags&DF_DONT_MODIFIED))
    DocWrite(doc1);

  if (DiffBins(doc1,doc2)) {
    "$RED$Bin Data is Different$FG$\n";
    res=TRUE;
  }

  DocDel(doc1);
  DocDel(doc2);
  Free(doc_sorted1);
  Free(doc_sorted2);
  Free(doc_unsorted1);
  Free(doc_unsorted2);
  if (_df_flags)
    *_df_flags=df_flags;
  return res;
}

#help_index "Cmd Line (Typically);Info"
I64 ZRepCompare(CDirEntry *e1,CDirEntry *e2)
{
  return SignI64(e2->user_data(F64)-e1->user_data(F64));
}
public U0 ZipRep(U8 *files_find_mask="/*",U8 *fu_flags=NULL,
  Bool just_text_not_graphics=TRUE)
{//Report file compressibility.
//Dft is just src files.  (Used to spot
  //src files with redundancy in them.)
  I64 i=0,cnt,size,fuf_flags=0;
  CDirEntry *tmpde,*tmpde1,**sort_buf;
  U8 *buf;
  CArcCompress *arc;
  ScanFlags(&fuf_flags,Define("ST_FILE_UTIL_FLAGS"),"+r+F+S");
  ScanFlags(&fuf_flags,Define("ST_FILE_UTIL_FLAGS"),fu_flags);
  tmpde=tmpde1=FilesFind(files_find_mask,fuf_flags);
  cnt=FileCnt(tmpde);
  sort_buf=MAlloc(sizeof(CDirEntry *)*cnt);
  while (tmpde) {
    sort_buf[i++]=tmpde;
    buf=FileRead(tmpde->full_name,&size);
    if (just_text_not_graphics)
      size=StrLen(buf);
    arc=CompressBuf(buf,size);
    Free(buf);
    tmpde->user_data(F64)=100.0*arc->compressed_size/arc->expanded_size;
    Free(arc);
    tmpde=tmpde->next;
  }
  QSortI64(sort_buf,cnt,&ZRepCompare);
  for (i=0;i<cnt;i++) {
    "%4.1f%% %04X ",sort_buf[i]->user_data,sort_buf[i]->size;
    PutFileLink(sort_buf[i]->full_name);
    '\n';
  }
  Free(sort_buf);
  DirTreeDel(tmpde1);
}