/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla Communicator client code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2001
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Brian Stell <bstell@netscape.com>
 *
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "prmem.h"
#include "nsCompressedCharMap.h"
#include "nsCRT.h"
#include "nsICharRepresentable.h"

void
FreeCCMap(PRUint16* &aMap)
{
  if (!aMap)
    return;
  PR_Free(aMap - CCMAP_EXTRA);
  aMap = nsnull;
}

PRUint16*
MapToCCMap(PRUint32* aMap)
{
  // put the data into a temp map
  nsCompressedCharMap ccmapObj;
  ccmapObj.SetChars(aMap);

  PRUint16 *ccmap = (PRUint16*)PR_Malloc((CCMAP_EXTRA + ccmapObj.GetSize()) * sizeof(PRUint16));
  NS_ASSERTION(ccmap, "failed to alloc new CCMap");

  if (!ccmap)
    return nsnull;

  ccmap += CCMAP_EXTRA;
  CCMAP_SIZE(ccmap) = ccmapObj.GetSize();
  CCMAP_FLAG(ccmap) = CCMAP_NONE_FLAG;

  ccmapObj.FillCCMap(ccmap);

#ifdef DEBUG
  for (int i=0; i<NUM_UNICODE_CHARS; i++) {
    PRBool oldb = IS_REPRESENTABLE(aMap, i);
    PRBool newb = CCMAP_HAS_CHAR(ccmap, i);
    if ((oldb) != (newb)) {
      NS_ASSERTION(oldb==newb,"failed to generate map correctly");
    }
  }
#endif
  return ccmap;
}

PRUint16* CreateEmptyCCMap()
{
  PRUint16 *ccmap = (PRUint16*)PR_Malloc((CCMAP_EXTRA + 16) * sizeof(PRUint16));
  NS_ASSERTION(ccmap, "failed to alloc new CCMap");

  if (!ccmap)
    return nsnull;

  memset(ccmap, '\0', CCMAP_EMPTY_SIZE_PER_INT16 * sizeof(PRUint16)+ CCMAP_EXTRA);
  ccmap += CCMAP_EXTRA;
  CCMAP_SIZE(ccmap) = CCMAP_EMPTY_SIZE_PER_INT16;
  CCMAP_FLAG(ccmap) = CCMAP_NONE_FLAG;
  return ccmap;
}

PRUint16*
MapperToCCMap(nsICharRepresentable *aMapper)
{
  PRUint32 map[UCS2_MAP_LEN];
  memset(map, 0, sizeof(map));
  nsresult res = aMapper->FillInfo(map);
  if (NS_FAILED(res))
    return nsnull;
  PRUint16* ccMap = MapToCCMap(map);

  return ccMap;
}

PRBool
NextNonEmptyCCMapPage(PRUint16* aCCMap, PRUint16 *aPageStart)
{
  int i, j;
  unsigned int k;
  
  //
  // Point to the next page
  //
  unsigned int upper_index;
  unsigned int mid_index;
  if (*aPageStart == CCMAP_BEGIN_AT_START_OF_MAP) {
    upper_index = 0;
    mid_index   = 0;
  }
  else {
    upper_index = CCMAP_UPPER_INDEX(*aPageStart);
    mid_index   = CCMAP_MID_INDEX(*aPageStart) + 1;
  }


  // walk thru the upper pointers
  PRUint16 *upper = &aCCMap[0];
  for (i=upper_index; i<CCMAP_NUM_UPPER_POINTERS; i++, mid_index=0) {
    if (upper[i] == CCMAP_EMPTY_MID) {
      continue;
    }

    // walk the mid array
    PRUint16 *mid = &aCCMap[upper[i]];
    for (j=mid_index; j<CCMAP_NUM_MID_POINTERS; j++) {
      if (mid[j] == CCMAP_EMPTY_PAGE)
        continue;

      // walk the page
      ALU_TYPE *page = (ALU_TYPE*)&aCCMap[mid[j]];
      for (k=0; k<CCMAP_NUM_ALUS_PER_PAGE; k++) {
        if (page[k] != 0) {
          PRUint32 base = (i*CCMAP_NUM_UCHARS_PER_MID) + (j*CCMAP_NUM_UCHARS_PER_PAGE);
          NS_ASSERTION(base<NUM_UNICODE_CHARS, "invalid page address");
          *aPageStart = (PRUint16)base;
          return PR_TRUE;
        }
      }
    }
  }
  return PR_FALSE;
}

#define CCMAP_MID_OFFSET(m, i) ((m)[i])
#define CCMAP_PAGE_OFFSET_FROM_MIDOFFSET(m, midoffset, i) ((m)[(i) + (midoffset)])

/***********************************************************************************
 *compare 2 ccmap and see if they are exactly the same
 * Here I assume both ccmap is generated by 
 * nsCompressedCharMap::SetChars(PRUint32* aMap)
 * This funtion rely on current implementation of above function. The that changed, 
 * we might need to revisit this implementation. 
 ***********************************************************************************/
PRBool IsSameCCMap(PRUint16* ccmap1, PRUint16* ccmap2)
{
  PRUint16 len1 = CCMAP_SIZE(ccmap1);
  PRUint16 len2 = CCMAP_SIZE(ccmap2);
  
  if (len1 != len2)
    return PR_FALSE;

  if (memcmp(ccmap1, ccmap2, sizeof(PRUint16)*len1))
    return PR_FALSE;
  return PR_TRUE;
}

PRUint16*
nsCompressedCharMap::NewCCMap()
{
  PRUint16 *newMap = (PRUint16*)PR_Malloc((CCMAP_EXTRA + mUsedLen) * sizeof(PRUint16));
  NS_ASSERTION(newMap, "failed to alloc new CCMap");
  if (!newMap)
    return nsnull;
  
  newMap += CCMAP_EXTRA;
  CCMAP_SIZE(newMap) = GetSize();
  CCMAP_FLAG(newMap) = CCMAP_NONE_FLAG;

  FillCCMap(newMap);
  return newMap;
}

PRUint16*
nsCompressedCharMap::FillCCMap(PRUint16* aCCMap)
{
  // transfer the data
  for (int i=0; i<mUsedLen; i++)
    aCCMap[i] = u.mCCMap[i];

  return aCCMap;
}

nsCompressedCharMap::nsCompressedCharMap()
{
  // initialize map to have:
  //    1 upper pointer array
  //    1 empty mid pointer array
  //    1 empty page

  int i;
  memset(u.mCCMap, 0, sizeof(u.mCCMap));
  mUsedLen = 0;
  mAllOnesPage = 0;

  // init the upper pointers
  PRUint16 *upper = &u.mCCMap[0];
  for (i=0; i<CCMAP_NUM_UPPER_POINTERS; i++) {
    upper[i] = CCMAP_EMPTY_MID;
  }
  mUsedLen += CCMAP_NUM_UPPER_POINTERS;

  // init the empty mid
  NS_ASSERTION(mUsedLen==CCMAP_EMPTY_MID, "empty mid offset misconfigured");
  PRUint16 *mid = &u.mCCMap[CCMAP_EMPTY_MID];
  for (i=0; i<CCMAP_NUM_MID_POINTERS; i++) {
    mid[i] = CCMAP_EMPTY_PAGE;
  }
  mUsedLen += CCMAP_NUM_MID_POINTERS;

  // init the empty page
  NS_ASSERTION(mUsedLen==CCMAP_EMPTY_PAGE, "empty page offset misconfigured");
  // the page was zero'd by the memset above
  mUsedLen += CCMAP_NUM_PRUINT16S_PER_PAGE;
}

void
nsCompressedCharMap::SetChar(PRUint16 aChar)
{
  unsigned int i;
  unsigned int upper_index      = CCMAP_UPPER_INDEX(aChar);
  unsigned int mid_index        = CCMAP_MID_INDEX(aChar);

  PRUint16 mid_offset = u.mCCMap[upper_index];
  if (mid_offset == CCMAP_EMPTY_MID) {
    mid_offset = u.mCCMap[upper_index] = mUsedLen;
    mUsedLen += CCMAP_NUM_MID_POINTERS;
    NS_ASSERTION(mUsedLen<=CCMAP_MAX_LEN,"length too long");
    // init the mid
    PRUint16 *mid = &u.mCCMap[mid_offset];
    for (i=0; i<CCMAP_NUM_MID_POINTERS; i++) {
      NS_ASSERTION(mid[i]==0, "this mid pointer should be unused");
      mid[i] = CCMAP_EMPTY_PAGE;
    }
  }

  PRUint16 page_offset = u.mCCMap[mid_offset+mid_index];
  if (page_offset == CCMAP_EMPTY_PAGE) {
    page_offset = u.mCCMap[mid_offset+mid_index] = mUsedLen;
    mUsedLen += CCMAP_NUM_PRUINT16S_PER_PAGE;
    NS_ASSERTION(mUsedLen<=CCMAP_MAX_LEN,"length too long");
    // init the page
    PRUint16 *page = &u.mCCMap[page_offset];
    for (i=0; i<CCMAP_NUM_PRUINT16S_PER_PAGE; i++) {
      NS_ASSERTION(page[i]==0, "this page should be unused");
      page[i] = 0;
    }
  }
#undef CCMAP_SET_CHAR
#define CCMAP_SET_CHAR(m,c) (CCMAP_TO_ALU(m,c) |= (CCMAP_POW2(CCMAP_BIT_INDEX(c))))
  CCMAP_SET_CHAR(u.mCCMap,aChar);
#undef CCMAP_SET_CHAR
  NS_ASSERTION(CCMAP_HAS_CHAR(u.mCCMap,aChar), "failed to set bit");
}

void
nsCompressedCharMap::SetChars(PRUint16 aBase, ALU_TYPE* aPage)
{
  unsigned int i;
  unsigned int upper_index = CCMAP_UPPER_INDEX(aBase);
  unsigned int mid_index   = CCMAP_MID_INDEX(aBase);
  NS_ASSERTION((aBase&CCMAP_PAGE_MASK)==0, "invalid page address");

  //
  // check of none/all bits set
  //
  PRUint16 num_none_set = 0;
  PRUint16 num_all_set = 0;
  for (i=0; i<CCMAP_NUM_ALUS_PER_PAGE; i++) {
    if (aPage[i] == 0)
      num_none_set++;
    else if (aPage[i] == CCMAP_ALU_MASK)
      num_all_set++;
  }
  if (num_none_set == CCMAP_NUM_ALUS_PER_PAGE) {
    return;
  }

  //
  // Alloc mid if necessary
  //
  PRUint16 mid_offset = u.mCCMap[upper_index];
  if (mid_offset == CCMAP_EMPTY_MID) {
    mid_offset = u.mCCMap[upper_index] = mUsedLen;
    mUsedLen += CCMAP_NUM_MID_POINTERS;
    NS_ASSERTION(mUsedLen<=CCMAP_MAX_LEN,"length too long");
    // init the mid
    PRUint16 *mid = &u.mCCMap[mid_offset];
    for (i=0; i<CCMAP_NUM_MID_POINTERS; i++) {
      NS_ASSERTION(mid[i]==0, "this mid pointer should be unused");
      mid[i] = CCMAP_EMPTY_PAGE;
    }
  }

  //
  // if all bits set share an "all bits set" page
  //
  if (num_all_set == CCMAP_NUM_ALUS_PER_PAGE) {
    if (mAllOnesPage == 0) {
      mAllOnesPage = mUsedLen;
      mUsedLen += CCMAP_NUM_PRUINT16S_PER_PAGE;
      NS_ASSERTION(mUsedLen<=CCMAP_MAX_LEN,"length too long");
      ALU_TYPE *all_ones_page = (ALU_TYPE*)&u.mCCMap[mAllOnesPage];
      for (i=0; i<CCMAP_NUM_ALUS_PER_PAGE; i++) {
        NS_ASSERTION(all_ones_page[i]==0, "this page should be unused");
        all_ones_page[i] = CCMAP_ALU_MASK;
      }
    }
    u.mCCMap[mid_offset+mid_index] = mAllOnesPage;
    return;
  }

  //
  // Alloc page if necessary
  //
  PRUint16 page_offset = u.mCCMap[mid_offset+mid_index];
  if (page_offset == CCMAP_EMPTY_PAGE) {
    page_offset = u.mCCMap[mid_offset+mid_index] = mUsedLen;
    mUsedLen += CCMAP_NUM_PRUINT16S_PER_PAGE;
    NS_ASSERTION(mUsedLen<=CCMAP_MAX_LEN,"length too long");
  }

  // copy the page data
  ALU_TYPE *page = (ALU_TYPE*)&u.mCCMap[page_offset];
  for (i=0; i<CCMAP_NUM_ALUS_PER_PAGE; i++) {
    NS_ASSERTION(page[i]==0, "this page should be unused");
    page[i] = aPage[i];
  }
}

void
nsCompressedCharMap::SetChars(PRUint16* aCCMap)
{
  int i, j;

  //
  // Copy the input CCMap
  //
  // walk thru the upper pointers
  PRUint16 *upper = &aCCMap[0];
  for (i=0; i<CCMAP_NUM_UPPER_POINTERS; i++) {
    if (upper[i] == CCMAP_EMPTY_MID)
      continue;

    // walk the mid array
    PRUint16 *mid = &aCCMap[upper[i]];
    for (j=0; j<CCMAP_NUM_MID_POINTERS; j++) {
      if (mid[j] == CCMAP_EMPTY_PAGE)
        continue;

      PRUint32 base = (i*CCMAP_NUM_UCHARS_PER_MID) + (j*CCMAP_NUM_UCHARS_PER_PAGE);
      NS_ASSERTION(base<NUM_UNICODE_CHARS, "invalid page address");
      ALU_TYPE *page = (ALU_TYPE*)&aCCMap[mid[j]];
      SetChars((PRUint16)base, page);
    }
  }
}

void
nsCompressedCharMap::SetChars(PRUint32* aMap)
{
  PRUint32* frommap_page;
  frommap_page = aMap;
  PRUint16 base = 0;

  for (int i=0; i<CCMAP_TOTAL_PAGES; i++) {

#if (CCMAP_BITS_PER_ALU == CCMAP_BITS_PER_PRUINT32)
    SetChars(base, (ALU_TYPE*)frommap_page);
    frommap_page += CCMAP_PRUINT32S_PER_PAGE;

#elif (CCMAP_BITS_PER_ALU > CCMAP_BITS_PER_PRUINT32)
    int j, k = CCMAP_BITS_PER_PRUINT32;
    ALU_TYPE page[CCMAP_NUM_ALUS_PER_PAGE];
    ALU_TYPE *p = page;
    for (j=0; j<CCMAP_ALUS_PER_PAGE; j++) {
      ALU_TYPE alu_val = 0;
      ALU_TYPE tmp;
      for (k=0; k<CCMAP_PRUINT32S_PER_ALU; k++) {
        tmp = *frommap_page;
        tmp <<= (k*CCMAP_BITS_PER_PRUINT32);
        //alu_val |= (*frommap_page)<<(k*CCMAP_BITS_PER_PRUINT32);
        alu_val |= tmp;
        frommap_page++;
      }
      *p++ = alu_val;
    }
    SetChars(base, page);
#elif (CCMAP_BITS_PER_ALU < CCMAP_BITS_PER_PRUINT32)
    int j, k;
    ALU_TYPE page[CCMAP_NUM_ALUS_PER_PAGE];
    int v = CCMAP_PRUINT32S_PER_PAGE;
    ALU_TYPE *p = page;
    for (j=0; j<CCMAP_PRUINT32S_PER_PAGE; j++) {
      PRUint32 pruint32_val = *frommap_page++;
      for (k=0; k<CCMAP_ALUS_PER_PRUINT32; k++) {
        *p++ = pruint32_val & CCMAP_ALU_MASK;
        pruint32_val >>= CCMAP_BITS_PER_ALU; 
      }
    }
    SetChars(base, page);
#endif

    base += CCMAP_NUM_UCHARS_PER_PAGE;
  }
}

#ifdef DEBUG
void
printCCMap(PRUint16* aCCMap)
{
  PRUint16 page = CCMAP_BEGIN_AT_START_OF_MAP;
  while (NextNonEmptyCCMapPage(aCCMap, &page)) {
    //FONT_SCAN_PRINTF(("page starting at 0x%04x has chars", page));
    int i;
    PRUint32 pagechar = page;
  
    printf("CCMap:0x%04lx=", (long)page);
    for (i=0; i<(CCMAP_BITS_PER_PAGE/8); i++) {
      unsigned char val = 0;
      for (int j=0; j<8; j++) {
        if (CCMAP_HAS_CHAR(aCCMap, pagechar)) {
          val |= 1 << j;
        }
        pagechar++;
      }
      printf("%02x", val);
    }
    printf("\n");
  }
}
#endif

// Non-BMP unicode support extension, create ccmap for both BMP and extended planes 
PRUint16*
MapToCCMapExt(PRUint32* aBmpPlaneMap, PRUint32** aOtherPlaneMaps, PRUint32 aOtherPlaneNum)
{
  nsCompressedCharMap* otherPlaneObj[EXTENDED_UNICODE_PLANES];
  PRUint32 totalSize;
  PRUint16 i;
  PRUint32 *planeCCMapOffsets;
  PRUint32 currOffset;

  NS_ASSERTION(aOtherPlaneNum <= EXTENDED_UNICODE_PLANES, "illegal argument value");
  if (aOtherPlaneNum > EXTENDED_UNICODE_PLANES)
    return nsnull;

  // Put the data into a temp map
  nsCompressedCharMap bmpCcmapObj;
  bmpCcmapObj.SetChars(aBmpPlaneMap);

  // Add bmp size
  totalSize = bmpCcmapObj.GetSize();

  // Add bmp length field
  totalSize += CCMAP_EXTRA;
  
  // Add Plane array 
  totalSize += EXTENDED_UNICODE_PLANES * sizeof(PRUint32)/sizeof(PRUint16);

  // Add an empty plane ccmap
  // A totally empty plane ccmap can be represented by 16 *(PRUint16)0. 
  totalSize += CCMAP_EMPTY_SIZE_PER_INT16;

  // Create ccmap for other planes
  for (i = 0; i < aOtherPlaneNum; i++) {
    if (aOtherPlaneMaps[i]) {
      otherPlaneObj[i] = new nsCompressedCharMap();
      NS_ASSERTION(otherPlaneObj, "unable to create new nsCompressedCharMap");
      if(otherPlaneObj) {
        otherPlaneObj[i]->SetChars(aOtherPlaneMaps[i]);
        totalSize += otherPlaneObj[i]->GetSize();
      }
    } else {
      otherPlaneObj[i] = nsnull;
    }
  }

  PRUint16 *ccmap = (PRUint16*)PR_Malloc(totalSize * sizeof(PRUint16));
  NS_ASSERTION(ccmap, "failed to alloc new CCMap");

  if (!ccmap)
    return nsnull;

  // Assign BMP ccmap size
  ccmap += CCMAP_EXTRA;
  CCMAP_SIZE(ccmap) = bmpCcmapObj.GetSize();
  CCMAP_FLAG(ccmap) = CCMAP_SURROGATE_FLAG;

  // Fill bmp plane ccmap 
  bmpCcmapObj.FillCCMap(ccmap);

  // Get pointer for plane ccmap offset array
  currOffset = bmpCcmapObj.GetSize();
  planeCCMapOffsets = (PRUint32*)(ccmap+currOffset);
  currOffset += sizeof(PRUint32)/sizeof(PRUint16)*EXTENDED_UNICODE_PLANES;

  // Put a empty ccmap there 
  memset(ccmap+currOffset, '\0', sizeof(PRUint16)*16);
  PRUint32 emptyCCMapOffset = currOffset;
  currOffset += CCMAP_EMPTY_SIZE_PER_INT16;

  // Now fill all rest of the planes' ccmap and put off in array
  for (i = 0; i <aOtherPlaneNum; i++) {
    if (aOtherPlaneMaps[i] && otherPlaneObj[i]) {
      *(planeCCMapOffsets+i) = currOffset;
      otherPlaneObj[i]->FillCCMap(ccmap+currOffset);
      currOffset += otherPlaneObj[i]->GetSize();
    }
    else 
      *(planeCCMapOffsets+i) = emptyCCMapOffset;
  }
  for (; i < EXTENDED_UNICODE_PLANES; i++) {
    *(planeCCMapOffsets+i) = emptyCCMapOffset;
  }

  // remove all nsCompressedCharMap objects allocated 
  for (i = 0; i < aOtherPlaneNum; i++) {
    if (otherPlaneObj[i]) 
      delete otherPlaneObj[i];
  }

#ifdef DEBUG
  PRUint32 k, h, l, plane, offset;
  PRBool oldb;
  PRBool newb;

  // testing for BMP plane
  for (k=0; k<NUM_UNICODE_CHARS; k++) {
    oldb = IS_REPRESENTABLE(aBmpPlaneMap, k);
    newb = CCMAP_HAS_CHAR(ccmap, k);
    NS_ASSERTION(oldb==newb,"failed to generate map correctly");
  }

  //testing for extension plane 
  for (k = 0x10000; k < 0x100000; k++) {
    plane = k/0x10000;
    if (plane > aOtherPlaneNum)
      break;
    if (aOtherPlaneMaps[plane-1])
      oldb = IS_REPRESENTABLE(aOtherPlaneMaps[plane-1], k&0xffff);
    else
      oldb = 0;
    newb = CCMAP_HAS_CHAR_EXT(ccmap, k);
    NS_ASSERTION(oldb==newb, "failed to generate extension map correctly");
  }

  
  // testing for non-BMP plane
    for (h = 0; h < 0x400; h++) {
      for (l = 0; l < 0x400; l++) {
        plane = h >> 6;
        offset = (h*0x400 + l) & 0xffff;
        if (aOtherPlaneMaps[plane])
          oldb = IS_REPRESENTABLE(aOtherPlaneMaps[plane], offset);
        else
          oldb = 0;
        newb = CCMAP_HAS_CHAR_EXT2(ccmap, h+0xd800, l+0xdc00);
        NS_ASSERTION(oldb==newb, "failed to generate extension map correctly");
      }
    }
#endif

  return ccmap;
}
