// Filename: rangeDescription.cxx
// Created by:  drose (07Sep03)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "rangeDescription.h"
#include "string_utils.h"
#include "pnotify.h"

////////////////////////////////////////////////////////////////////
//     Function: RangeDescription::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
RangeDescription::
RangeDescription() {
}

////////////////////////////////////////////////////////////////////
//     Function: RangeDescription::parse_parameter
//       Access: Public
//  Description: Parses a string of comma- and hyphen-delimited
//               unicode values, in decimal and/or hex, including
//               possible bracket-delimited ASCII characters, as may
//               have been passed on a command line.  Returns true if
//               the parameter is parsed correctly, false otherwise.
////////////////////////////////////////////////////////////////////
bool RangeDescription::
parse_parameter(const string &param) {
  // First, go through and separate the string by commas.  We have to
  // do this by hand instead of calling tokenize(), because we also
  // have to scan for square brackets, which may contain nested
  // commas.
  size_t p = 0;
  while (p < param.length()) {
    size_t q = param.find_first_of("[,", p);
    if (q == string::npos) {
      return parse_word(trim(param.substr(p)));
    }
    if (!parse_word(trim(param.substr(p, q - p)))) {
      return false;
    }

    if (param[q] == '[') {
      // A square bracket means we must search for the matching square
      // bracket.  However, a right bracket immediately after the left
      // bracket doesn't count; we start the scan after that.
      p = param.find("]", q + 2);
      if ( p == string::npos) {
        nout << "Unclosed open bracket.\n";
        return false;
      }
      if (!parse_bracket(param.substr(q + 1, p - q - 1))) {
        return false;
      }
      p = p + 1;

    } else {
      // Otherwise, if the separator was just a comma, the next
      // character begins the next word.
      p = q + 1;
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: RangeDescription::output
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
void RangeDescription::
output(ostream &out) const {
  bool first_time = true;
  RangeList::const_iterator ri;
  for (ri = _range_list.begin(); ri != _range_list.end(); ++ri) {
    const Range &range = (*ri);
    if (!first_time) {
      out << ",";
    }
    first_time = false;
    if (range._from_code == range._to_code) {
      out << range._from_code;
    } else {
      out << range._from_code << "-" << range._to_code;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: RangeDescription::parse_word
//       Access: Private
//  Description: Parses a single "word", i.e. the text delimited by
//               commas, that might be listed on the command line.
//               This is generally either the empty string, a single
//               number, or a pair of numbers separated by a hyphen.
////////////////////////////////////////////////////////////////////
bool RangeDescription::
parse_word(const string &word) {
  if (word.empty()) {
    return true;
  }

  // It's not empty, so see if it includes a hyphen.
  size_t hyphen = word.find('-');
  if (hyphen == string::npos) {
    // Nope, just one number.
    int code;
    if (!parse_code(word, code)) {
      return false;
    }
    add_singleton(code);

  } else {
    // Two numbers separated by a hyphen.
    int from_code, to_code;
    if (!parse_code(word.substr(0, hyphen), from_code)) {
      return false;
    }
    if (!parse_code(word.substr(hyphen + 1), to_code)) {
      return false;
    }
    add_range(from_code, to_code);
  }
  
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: RangeDescription::parse_code
//       Access: Private
//  Description: Parses a single numeric value, either decimal or
//               hexadecimal, and stores it in the indicated
//               parameter.  Returns true if successful, false
//               otherwise.
////////////////////////////////////////////////////////////////////
bool RangeDescription::
parse_code(const string &word, int &code) { 
  string str = trim(word);
  const char *nptr = str.c_str();
  char *endptr;
  code = strtol(nptr, &endptr, 0);
  if (*endptr == '\0') {
    return true;
  }

  nout << "Invalid Unicode value: " << word << "\n";
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: RangeDescription::parse_bracket
//       Access: Private
//  Description: Parses the text listed between square brackets on the
//               command line.
////////////////////////////////////////////////////////////////////
bool RangeDescription::
parse_bracket(const string &str) {
  string::const_iterator si;
  si = str.begin();
  while (si != str.end()) {
    int ch = (*si);
    ++si;
    if (si != str.end() && (*si) == '-') {
      // A hyphen indicates a range.
      ++si;
      if (si == str.end()) {
        // Unless the hyphen is the last character.
        add_singleton(ch);
        add_singleton('-');
      } else {
        add_range(ch, (*si));
        ++si;
      }
    } else {
      // Anything other than a hyphen indicates a singleton.
      add_singleton(ch);
    }
  }

  return true;
}
