// file: $isip/class/algo/Mask/mask_06.cc
// version: $Id: mask_06.cc 7547 2001-12-21 19:43:18Z gao $
//

// isip include files
//
#include "Mask.h"

// method: set
//
// arguments:
//  const String& source: (input) mask string
//
// return: a boolean value indicating status
//
// this method converts a string into a mask vector
//
boolean Mask::set(const String& source_a) {

  // count the number of sub-strings separated by ','
  //
  long tokens = source_a.countTokens(L',');

  // get all the sub-strings separated by ','
  //
  String vals[tokens];
  for (long i = 0, pos = 0; i < tokens; i++) {
    source_a.tokenize(vals[i], pos, L",");
  }

  // compute the number of all the digits in input string
  //
  long num = 0;
  for (long i = 0; i < tokens; i++) {
    num += vals[i].countTokens(L'-');
  }

  // allocate the space for these digits
  //
  VectorLong vec(num);

  // declare the local variables
  //
  long k;
  long key = 0;
  String str[2];
  long val1 = 0;
  long val2 = 0;

  // loop through all the sub-strings
  //
  for (long i = 0; i < tokens; i++) {

    k = vals[i].countTokens(L'-');

    // input a range of the masked 1's
    //
    if (k == 2) {
      long pos = 0;
      vals[i].tokenize(str[0], pos, L'-');
      vals[i].tokenize(str[1], pos, L'-');      
      str[0].get(val1);
      vec(key++) = val1;
      str[1].get(val2);
      vec(key++) = val2;
    }

    // input a single mask flag 1
    //
    else if (k == 1) {
      vals[i].get(val1);
      vec(key++) = val1;
    }
    
    else {
      return Error::handle(name(), L"set", Error::ARG, __FILE__, __LINE__);
    }
  }

  // allocate space
  //
  mask_d.setLength(vec.max() + 1);

  // pad with all OFF's
  //
  mask_d.assign((byte)OFF);
  
  // loop through all the sub-strings
  //
  for (long i = 0; i < tokens; i++) {
    k = vals[i].countTokens(L'-');
    long pos = 0;
    
    // a range of masked ON's
    //
    if (k == 2) {
      vals[i].tokenize(str[0], pos, L'-');
      vals[i].tokenize(str[1], pos, L'-');	
      str[0].get(val1);
      str[1].get(val2);
      
      // put the preserved mask flag ON's
      //
      if (val1 > val2) {
	insert(mask_d, val2, val1 - val2 + 1, ON);
      }
      else if (val1 < val2) {
	insert(mask_d, val1, val2 - val1 + 1, ON);
      }
    }
    
    // k = 1: single index
    //
    else if (k == 1) {
      vals[i].get(val1);
      mask_d(val1) = ON;
    }
    else {
      return Error::handle(name(), L"set", Error::ARG,
			   __FILE__, __LINE__);
    }
  }

  // assign the mask string
  //
  source_d.assign(source_a);
  
  // exit gracefully
  //
  return true;
}

// method: set
//
// arguments:
//  const VectorByte& mask: (input) mask vector
//
// return: a boolean value indicating status
//
// this method sets the mask and converts it into a mask string
//
boolean Mask::set(const VectorByte& mask_a) {

  // set the mask
  //
  mask_d.assign(mask_a);

  // convert to a string
  //
  return convert(source_d, mask_d);
}

// method: convert
//
// arguments:
//  String& out: (output) mask string
//  const VectorByte& in: (input) mask vector
//
// return: a boolean value indicating status
//
// this method converts a byte-oriented mask to a string
//
boolean Mask::convert(String& out_a, const VectorByte& in_a) {

  // clear the output and determine the length of the input
  //
  out_a.clear();
  long len = in_a.length();

  // define our state machine
  //
  enum STATE {ZERO, ONE};
  STATE state = ZERO;
  boolean first = true;

  // keep track of the number of 1's in a row
  //
  long cur_len = 0;
  
  // the loop variable is kept outside the scope of the loop so we can
  // use it at the end
  //
  long index;
  for (index = 0; index < len; index++) {

    // detect a change from zero to one
    //
    if ((state == ZERO) && (in_a(index) != OFF)) {
      state = ONE;

      // for all but the first output we need a delimiter
      //
      if (first) {
	first = false;
      }
      else {
	out_a.concat(L",");
      }
      out_a.concat(index);
      cur_len = 1;
    }

    // for state 1, either increment the count if we get another ON or
    // transition to state 0 if we get a OFF.
    //
    else if (state == ONE) {
      if (in_a(index) == ON) {
	cur_len++;
      }

      // detect a change from one to zero
      //
      else {
	state = ZERO;

	// if there was more than one ON in a row we need to output the range
	//
	if (cur_len > 1) {
	  out_a.concat(L"-");
	  out_a.concat(index - 1);
	}
      }
    }
  }

  // if there was more than one ON in a row at the end we need to
  // output the range
  //
  if ((state == ONE) && (cur_len > 1)) {
    out_a.concat(L"-");
    out_a.concat(index - 1);
  }

  // exit gracefully
  //
  return true;
}

// method: insert
//
// arguments:
//  VectorByte& mask: (input/output) mask vector
//  long start_index: (input) start index
//  long num_elem: (input) number of elements
//  byte mode: (input) byte mode (0 or, 1)
//
// return: a boolean value indicating status
//
// this method inserts given byte mode (OFF or ON) in mask vector
//
boolean Mask::insert(VectorByte& mask_a, long start_index_a, long num_elem_a,
		     byte mode_a) {

  long len = mask_a.length();

  long end_index = start_index_a + num_elem_a;
  
  // check the argument
  //
  if (end_index > len) {
    return Error::handle(name(), L"insert", Error::ARG, __FILE__, __LINE__);
  }

  for (long i = start_index_a; i < end_index; i++) {
    mask_a(i) = mode_a;
  }
  
  // exit gracefully
  //
  return true;
}
