var copyrightUTF = "Copyright © 2000 Mark Davis. All Rights Reserved.";

// option constants
var STRICT = 0;
var ALLOW_ISOLATE = 1;
var ALLOW_ISOLATE_AND_IRREGULAR = 2;

// error constants
var OVERFLOW = -1;
var ILLEGAL = -2;
var DISALLOWED = -3;
var TRUNCATED = -4;

// ==============================================================

var errorNames = ["", "OVERFLOW", "ILLEGAL", "DISALLOWED", "TRUNCATED"];

var DEBUG_START = 0x110000;

var STRICTNESS = ALLOW_ISOLATE;

// ==============================================================

function test() {
  document.main.testOutput.value = "Starting Test\r\n";
  status = "Testing";
  var source = [0];
  var target = [0, 0, 0, 0];
  var roundTrip = [0];
  for (var i = 0; i <= 0x10FFFF; ++i) {
    if ((i & 0xFFF) == 0) status = i.toString(16);

    source[0] = i;

    if (i >= DEBUG_START) document.main.testOutput.value += show(source, 0, 1);

    var len = UTF32_UTF8(source, 0, 1, target, 0, 4);
    if (len < 0) {
      document.main.testOutput.value += "\r\nError: " + i.toString(16) 
        + " => " + errorNames[-len] + "(" + len + ")\r\n";
      break;
    }

    if (i >= DEBUG_START) document.main.testOutput.value += " => " + show(target, 0, len);

    var len2 = UTF8_UTF32(target, 0, len, roundTrip, 0, 1);
    if (len2 < 0) {
      document.main.testOutput.value += show(source, 0, 1) + " => " + show(target, 0, len) + "\r\n";
      document.main.testOutput.value += "\r\nError2: " + i.toString(16) 
        + " => " + errorNames[-len2] + "(" + len2 + ")\r\n";
      break;
    }
    if (len2 != 1 || roundTrip[0] != i) {
      document.main.testOutput.value += "\r\nError3: " 
        + show(source, 0, 1)
        + " => " + show(target, 0, len)
        + " => " + show(roundTrip, 0, len2)
        + "\r\n";
      break;
    }

    if (i >= DEBUG_START) document.main.testOutput.value += " -> " + show(roundTrip, 0, len2) + ";\r\n";
  }
  document.main.testOutput.value += "Test Done";
}

function show(input, iIndex, iEnd) {
  var result = "[";
  for (var i = iIndex; i < iEnd; ++i) {
    if (i != iIndex) result += ", ";
    result += input[i].toString(16);
  }
  return result + "]";
}

// ==============================================================

function UTF32_UTF16(input, iIndex, iEnd, output, oIndex, oEnd) {
  while (iIndex < iEnd) {
    if (oIndex >= oEnd) return OVERFLOW;
    var cp = input[iIndex++];

    // If we use strict conversions, we make extra checks

    if (STRICTNESS <= ALLOW_ISOLATE && cp >= 0xD800 && cp <= 0xDFFF) {
      if (STRICTNESS == STRICT) return DISALLOWED;
      // now test for irregulars
      if (cp < 0xDC00 && iIndex < iEnd) {
        var trail = input[iIndex];
        if (trail >= 0xDC00 && trail <= 0xDFFF) return DISALLOWED;
      }
    }

    // Normal cases

    if (cp <= 0xFFFF) {
      output[oIndex++] = cp;                  // normal
    } else if (cp <= 0x10FFFF) {
      output[oIndex++] = (cp >> 10) + 0xD7C0   // lead
      if (oIndex >= oEnd) return OVERFLOW;
      output[oIndex++] = (cp & 0x3FF) + 0xDC00 // trail
    } else {
      return ILLEGAL;
    }
  }
  return oIndex;
}

// ==============================================================

function UTF16_UTF32(input, iIndex, iEnd, output, oIndex, oEnd) {
  while (iIndex < iEnd) {
    if (oIndex >= oEnd) return OVERFLOW;
    var lead = input[iIndex++];

    if (lead < 0xD800 || lead > 0xDFFF) {
      output[oIndex++] = lead;                   // normal
    } else {
      if (lead < 0xDC00 && iIndex < iEnd) {
        var trail = input[iIndex];
        if (0xDC00 <= trail && trail <= 0xDFFF) {
          output[oIndex++] = (lead << 10) + trail - 0x35FDC00; // surrogate pair
          ++iIndex;                              // skip trail
          continue;                              // keep looping
        }
      }
      
      // If we use strict conversions, we make extra checks

      if (STRICTNESS != ALLOW_ISOLATE) return DISALLOWED;

      output[oIndex++] = lead;                   // normal
    }
  }
  return oIndex;
}

// ==============================================================

function UTF32_UTF8(input, iIndex, iEnd, output, oIndex, oEnd) {
  var back;
  while (iIndex < iEnd) {
    var cp = input[iIndex++];

    // If we use strict conversions, we make extra checks

    if (STRICTNESS <= ALLOW_ISOLATE && cp >= 0xD800 && cp <= 0xDFFF) {
      if (STRICTNESS == STRICT) return DISALLOWED;
      // now test for irregulars
      if (cp < 0xDC00 && iIndex < iEnd) {
        var trail = input[iIndex];
        if (trail >= 0xDC00 && trail <= 0xDFFF) return DISALLOWED;
      }
    }

    // Normal Cases

    if (cp <= 0x7F) {
      if (oIndex >= oEnd) return OVERFLOW;
      output[oIndex++] = cp;
    } else if (cp <= 0x7FF) {
      back = oIndex += 2;
      if (oIndex > oEnd) return OVERFLOW;
      output[--back] = 0x80 | (cp & 0x3F);
      output[--back] = 0xC0 | (cp >> 6);
    } else if (cp <= 0xFFFF) {
      back = oIndex += 3;
      if (oIndex > oEnd) return OVERFLOW;
      output[--back] = 0x80 | (cp & 0x3F);
      output[--back] = 0x80 | ((cp >>= 6) & 0x3F);
      output[--back] = 0xE0 | (cp >> 6);
    } else if (cp <= 0x10FFFF) {
      back = oIndex += 4;
      if (oIndex > oEnd) return OVERFLOW;
      output[--back] = 0x80 | (cp & 0x3F);
      output[--back] = 0x80 | ((cp >>= 6) & 0x3F);
      output[--back] = 0x80 | ((cp >>= 6) & 0x3F);
      output[--back] = 0xF0 | (cp >> 6);
    } else {
      return ILLEGAL;
    }
  }
  return oIndex;
}

// ==============================================================

function UTF8_UTF32(input, iIndex, iEnd, output, oIndex, oEnd) {
  while (iIndex < iEnd) {
    if (oIndex >= oEnd) return OVERFLOW;
    var cp = input[iIndex++];

    switch (cp & 0xF0) {
      // 0xxxxxxx
      case 0x00: case 0x10: case 0x20: case 0x30: case 0x40: case 0x50: case 0x60: case 0x70:
	output[oIndex++] = cp;
	break;
	
      // 110xxxxx
      case 0xC0: case 0xD0:
	cp &= 0x1F;
        if (iIndex >= iEnd) return TRUNCATED;

        var trail = input[iIndex++] ^ 0x80;
	if (trail > 0x3F) return ILLEGAL;
	cp = (cp << 6) + trail;

        if (cp < 0x80) return ILLEGAL;
        output[oIndex++] = cp;
	break;

      // 1110xxxx
      case 0xE0:
	cp &= 0xF;
        if (iIndex >= iEnd - 1) return TRUNCATED;
        var trail = input[iIndex++] ^ 0x80;
	if (trail > 0x3F) return ILLEGAL;
	cp = (cp << 6) + trail;

        var trail = input[iIndex++] ^ 0x80;
	if (trail > 0x3F) return ILLEGAL;
	cp = (cp << 6) + trail;

        if (cp < 0x800) return ILLEGAL;

        // Special test if we are strict

        if (STRICTNESS <= ALLOW_ISOLATE && cp >= 0xD800 && cp <= 0xDFFF) {
          if (STRICTNESS == STRICT) return DISALLOWED;
          // now test for irregulars
          if (cp < 0xDC00 && iIndex < iEnd - 1
              && input[iIndex] == 0xE0 && input[iIndex+1] > 0xB0) {
            return DISALLOWED;
          }
        }

        output[oIndex++] = cp;
        break;

      // 11110xxx
      case 0xF0:
        if (cp > 0xF4) return ILLEGAL;
	cp &= 0x7;
        if (iIndex >= iEnd - 2) return TRUNCATED;

        var trail = input[iIndex++] ^ 0x80;
	if (trail > 0x3F) return ILLEGAL;
	cp = (cp << 6) + trail;

        var trail = input[iIndex++] ^ 0x80;
	if (trail > 0x3F) return ILLEGAL;
	cp = (cp << 6) + trail;

        var trail = input[iIndex++] ^ 0x80;
	if (trail > 0x3F) return ILLEGAL;
	cp = (cp << 6) + trail;

        if (cp < 0x10000 || cp > 0x10FFFF) return ILLEGAL;
        output[oIndex++] = cp;
        break;

      // 10xxxxxx...
      default:
        return ILLEGAL;
    }
  }
  return oIndex;
}
