private Result<Map<String, ?>> decodeDictionary(byte[] bytes, int start) {
   Map<String, Object> result = new HashMap<>();
   int pos = start;
   while (bytes[pos] != 'e') {
     Result<?> k = read(bytes, pos);
     Result<?> v = read(bytes, k.getLast() + 1);
     result.put(new String((byte[]) k.getData()), v.getData());
     pos = v.getLast() + 1;
   }
   return new Result<>(result, pos);
 }
  @Override
  protected boolean parseMsg(String body, Data data) {
    if (!super.parseMsg(body, data)) return false;

    // If there is no city, see if we can find it in the info section
    if (data.strCity.length() == 0) {
      Result res = parseAddress(StartType.START_ADDR, FLAG_ONLY_CITY, data.strSupp);
      if (res.isValid()) res.getData(data);
    }

    // If city ends with " CO", expand it
    if (data.strCity.endsWith(" CO")) data.strCity += "UNTY";

    // ANd finally, see if this is in West Virginia
    if (WV_CITY_TABLE.contains(data.strCity)) data.strState = "WV";

    // See if there is a callback phone number in the APT field
    if (data.strApt.startsWith("CALLBK=")) {
      data.strPhone = data.strApt.substring(7).trim();
      data.strApt = "";
    }

    return true;
  }
  @Override
  protected boolean parseMsg(String subject, String body, Data data) {

    // New calls have a distinctive subject
    Matcher match = SUBJECT_PTN.matcher(subject);
    if (match.matches()) {
      data.strSource = match.group(1).trim();

      parseAddress(StartType.START_CALL, FLAG_FALLBACK_ADDR, body, data);
      if (data.strCall.length() == 0) {
        data.strCall = getLeft();
      } else {
        data.strSupp = getLeft();
      }
      return true;
    }

    // Otherwise drop back to the old logic
    // Not enough identification features to positively identify as a text page
    // so we require that identification be done externally
    if (!isPositiveId()) return false;

    // If no body (which gets to us as no subject) treat as special case
    if (subject.length() == 0) {
      Parser p = new Parser(body);
      parseAddress(
          StartType.START_CALL, FLAG_PREF_TRAILING_DIR | FLAG_ANCHOR_END, p.get("  "), data);
      if (data.strCall.length() == 0) data.strCall = p.get("  ");
      data.strSupp = p.get();
    } else {
      if (body.toUpperCase().startsWith(subject.toUpperCase())) {
        subject = body.substring(0, subject.length()).trim();
        body = body.substring(subject.length()).trim();
        if (body.toUpperCase().startsWith("AT ")) body = body.substring(3).trim();
      }

      Parser p = new Parser(subject);
      String sub = p.get("  ");
      Result res =
          parseAddress(
              StartType.START_CALL,
              FLAG_IMPLIED_INTERSECT | FLAG_PREF_TRAILING_DIR | FLAG_ANCHOR_END,
              sub);
      data.strPlace = p.get();

      if (res.isValid()) {
        res.getData(data);
        String[] flds = body.split("\n");
        data.strCall = flds[0].trim();
        for (int ii = 1; ii < flds.length; ii++) {
          data.strSupp = append(data.strSupp, " / ", flds[ii].trim());
        }
      } else {
        data.strCall = sub;
        parseAddress(
            StartType.START_ADDR, FLAG_IMPLIED_INTERSECT | FLAG_PREF_TRAILING_DIR, body, data);
        data.strSupp = getLeft();
      }
    }
    return true;
  }
  private Result execute(
      COMMAND_TYPE commandType,
      Command command,
      SortedEventList openTasks,
      SortedEventList completeTasks) {
    // TODO Auto-generated method stub
    Result r = new Result(false, null, null, null);
    switch (commandType) {
      case ADD:
        r = openTasks.addEvent(command.getEventFromCommand());
        r.setCommandType("add");
        return r;
      case GET:
        r = openTasks.getEventWithId(command.getId());
        r.setCommandType("get");
        return r;
      case SET:
        r = openTasks.editEventWithId(command.getEventFromCommand(), command.getId());
        r.setCommandType("set");
        return r;
      case DELETEOPEN:
        r = openTasks.deleteEventWithId(command.getId());
        r.setCommandType("deleteOpen");
        return r;
      case DELETECOMPLETE:
        r = completeTasks.deleteEventWithId(command.getId());
        r.setCommandType("deleteComplete");
        return r;
      case GETOPEN:
        r = openTasks.getArrayList();
        r.setCommandType("getOpen");
        return r;
      case GETCOMPLETE:
        r = completeTasks.getArrayList();
        r.setCommandType("getComplete");
        return r;
      case SORT:
        r = openTasks.sortBy(command.getSortKeyWords());
        r.setCommandType("sort");
        return r;
      case SEARCHOPEN:
        r = openTasks.searchBy(command.getSearchKeyWords(), command.getSearchType());
        r.setCommandType("SEARCHOPEN");
        return r;
      case SEARCHCOMPLETE:
        r = completeTasks.searchBy(command.getSearchKeyWords(), command.getSearchType());
        r.setCommandType("searchComplete");
        return r;
      case COMPLETE:
        r.setCommandType("COMPLETE");
        Result r1 =
            completeTasks.addEvent(openTasks.getEventWithId(command.getId()).getData().get(0));
        Result r2 = openTasks.deleteEventWithId(command.getId());
        if (r1.isSuccessful() && r2.isSuccessful()) {
          ArrayList<Event> data = new ArrayList<Event>();
          data.add(r1.getData().get(0));
          r.setData(r1.getData());
          r.setSuccessful(true);
          return r;
        } else {
          ArrayList<Event> data = new ArrayList<Event>();
          data.add(r1.getData().get(0));
          r.setSuccessful(false);
          r.setData(data);
          r.setError(INVALID_COMPLETED_TASK_ID_MSG);
          return r;
        }
      case INCOMPLETE:
        Result r3 =
            openTasks.addEvent(completeTasks.getEventWithId(command.getId()).getData().get(0));
        Result r4 = completeTasks.deleteEventWithId(command.getId());

        r.setCommandType("INCOMPLETE");
        if (r3.isSuccessful() && r4.isSuccessful()) {
          ArrayList<Event> data = new ArrayList<Event>();
          data.add(r3.getData().get(0));
          r.setData(r3.getData());
          r.setSuccessful(true);

          return r;
        } else {
          ArrayList<Event> data = new ArrayList<Event>();
          data.add(r3.getData().get(0));
          r.setSuccessful(false);
          r.setData(data);
          r.setError(INVALID_INCOMPLETE_TASK_ID_MSG);
          return r;
        }
      case SAVE:
        Boolean re1 = openTasks.write();
        Boolean re2 = completeTasks.write();
        r.setCommandType("save");
        if (re1 && re2) {
          return r;
        } else {
          r.setSuccessful(false);
          r.setError(IO_EXCEPTION_MSG);
          return r;
        }
      case BACKUP:
        r.setCommandType("backup");
        if (completeTasks.readBackUp().isSuccessful() && openTasks.readBackUp().isSuccessful()) {
          r.setSuccessful(true);
          return r;
        } else {
          r.setSuccessful(false);
          r.setError(IO_EXCEPTION_MSG);
          return r;
        }
      default:
        throw new Error("Unrecognized command type");
    }
  }