text: // automatically chop trees
  
  #include "uicommon.h"
  #include "listcolumn.h"
  
  #include "Core.h"
  #include "Console.h"
  #include "Export.h"
  #include "PluginManager.h"
  #include "DataDefs.h"
  #include "TileTypes.h"
  
  #include "df/burrow.h"
  #include "df/item.h"
  #include "df/item_flags.h"
  #include "df/items_other_id.h"
  #include "df/job.h"
  #include "df/map_block.h"
  #include "df/material.h"
  #include "df/plant.h"
  #include "df/plant_raw.h"
  #include "df/tile_dig_designation.h"
  #include "df/ui.h"
  #include "df/viewscreen_dwarfmodest.h"
  #include "df/world.h"
  
  #include "modules/Burrows.h"
  #include "modules/Designations.h"
  #include "modules/Gui.h"
  #include "modules/MapCache.h"
  #include "modules/Maps.h"
  #include "modules/Screen.h"
  #include "modules/World.h"
  
  #include <set>
  
  using std::string;
  using std::vector;
  using std::set;
  using namespace DFHack;
  using namespace df::enums;
  
  #define PLUGIN_VERSION 0.3
  DFHACK_PLUGIN("autochop");
  REQUIRE_GLOBAL(world);
  REQUIRE_GLOBAL(ui);
  
  static bool autochop_enabled = false;
  static int min_logs, max_logs;
  static const int LOG_CAP_MAX = 99999;
  static bool wait_for_threshold;
  struct Skip {
      bool fruit_trees;
      bool food_trees;
      bool cook_trees;
      operator int() {
          return (fruit_trees ? 1 : 0) |
                  (food_trees ? 2 : 0) |
                  (cook_trees ? 4 : 0);
      }
      Skip &operator= (int in) {
          // set all fields to false if they haven't been set in this save yet
          if (in < 0)
              in = 0;
          fruit_trees = (in & 1);
          food_trees = (in & 2);
          cook_trees = (in & 4);
          return *this;
      }
  };
  static Skip skip;
  
  static PersistentDataItem config_autochop;
  
  struct WatchedBurrow
  {
      int32_t id;
      df::burrow *burrow;
  
      WatchedBurrow(df::burrow *burrow) : burrow(burrow)
      {
          id = burrow->id;
      }
  };
  
  class WatchedBurrows
  {
  public:
      string getSerialisedIds()
      {
          validate();
          stringstream burrow_ids;
          bool append_started = false;
          for (auto it = burrows.begin(); it != burrows.end(); it++)
          {
              if (append_started)
                  burrow_ids << " ";
              burrow_ids << it->id;
              append_started = true;
          }
  
          return burrow_ids.str();
      }
  
      void clear()
      {
          burrows.clear();
      }
  
      void add(const int32_t id)
      {
          if (!isValidBurrow(id))
              return;
  
          WatchedBurrow wb(getBurrow(id));
          burrows.push_back(wb);
      }
  
      void add(const string burrow_ids)
      {
          istringstream iss(burrow_ids);
          int id;
          while (iss >> id)
          {
              add(id);
          }
      }
  
      bool isValidPos(const df::coord &plant_pos)
      {
          validate();
          if (!burrows.size())
              return true;
  
          for (auto it = burrows.begin(); it != burrows.end(); it++)
          {
              df::burrow *burrow = it->burrow;
              if (Burrows::isAssignedTile(burrow, plant_pos))
                  return true;
          }
  
          return false;
      }
  
      bool isBurrowWatched(const df::burrow *burrow)
      {
          validate();
          for (auto it = burrows.begin(); it != burrows.end(); it++)
          {
              if (it->burrow == burrow)
                  return true;
          }
  
          return false;
      }
  
  private:
      static bool isValidBurrow(const int32_t id)
      {
          return getBurrow(id);
      }
  
      static df::burrow *getBurrow(const int32_t id)
      {
          return df::burrow::find(id);
      }
  
      void validate()
      {
          for (auto it = burrows.begin(); it != burrows.end();)
          {
              if (!isValidBurrow(it->id))
                  it = burrows.erase(it);
              else
                  ++it;
          }
      }
  
      vector<WatchedBurrow> burrows;
  };
  
  static WatchedBurrows watchedBurrows;
  
  static int string_to_int(string s, int default_ = 0)
  {
      try
      {
          return std::stoi(s);
      }
      catch (std::exception&)
      {
          return default_;
      }
  }
  
  static void save_config()
  {
      config_autochop.val() = watchedBurrows.getSerialisedIds();
      config_autochop.ival(0) = autochop_enabled;
      config_autochop.ival(1) = min_logs;
      config_autochop.ival(2) = max_logs;
      config_autochop.ival(3) = wait_for_threshold;
      config_autochop.ival(4) = skip;
  }
  
  static void initialize()
  {
      watchedBurrows.clear();
      autochop_enabled = false;
      min_logs = 80;
      max_logs = 100;
      wait_for_threshold = false;
      skip = 0;
  
      config_autochop = World::GetPersistentData("autochop/config");
      if (config_autochop.isValid())
      {
          watchedBurrows.add(config_autochop.val());
          autochop_enabled = config_autochop.ival(0);
          min_logs = config_autochop.ival(1);
          max_logs = config_autochop.ival(2);
          wait_for_threshold = config_autochop.ival(3);
          skip = config_autochop.ival(4);
      }
      else
      {
          config_autochop = World::AddPersistentData("autochop/config");
          if (config_autochop.isValid())
              save_config();
      }
  }
  
  static bool skip_plant(const df::plant * plant, bool *restricted)
  {
      if (restricted)
          *restricted = false;
  
      // Skip all non-trees immediately.
      if (plant->flags.bits.is_shrub)
          return true;
  
      // Skip plants with invalid tile.
      df::map_block *cur = Maps::getTileBlock(plant->pos);
      if (!cur)
          return true;
  
      int x = plant->pos.x % 16;
      int y = plant->pos.y % 16;
  
      // Skip all unrevealed plants.
      if (cur->designation[x][y].bits.hidden)
          return true;
  
      df::tiletype_material material = tileMaterial(cur->tiletype[x][y]);
      if (material != tiletype_material::TREE)
          return true;
  
      const df::plant_raw *plant_raw = df::plant_raw::find(plant->material);
  
      // Skip fruit trees if set.
      if (skip.fruit_trees && plant_raw->material_defs.type[plant_material_def::drink] != -1)
      {
          if (restricted)
              *restricted = true;
          return true;
      }
  
      if (skip.food_trees || skip.cook_trees)
      {
          for (df::material * mat : plant_raw->material)
          {
              if (skip.food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW))
              {
                  if (restricted)
                      *restricted = true;
                  return true;
              }
  
              if (skip.cook_trees && mat->flags.is_set(material_flags::EDIBLE_COOKED))
              {
                  if (restricted)
                      *restricted = true;
                  return true;
              }
          }
      }
  
      return false;
  }
  
  static int do_chop_designation(bool chop, bool count_only, int *skipped = nullptr)
  {
      int count = 0;
      if (skipped)
      {
          *skipped = 0;
      }
      for (size_t i = 0; i < world->plants.all.size(); i++)
      {
          const df::plant *plant = world->plants.all[i];
  
          bool restricted = false;
          if (skip_plant(plant, &restricted))
          {
              if (restricted && skipped)
              {
                  ++*skipped;
              }
              continue;
          }
  
          if (!count_only && !watchedBurrows.isValidPos(plant->pos))
              continue;
  
          if (chop && !Designations::isPlantMarked(plant))
          {
              if (count_only)
              {
                  if (Designations::canMarkPlant(plant))
                      count++;
              }
              else
              {
                  if (Designations::markPlant(plant))
                      count++;
              }
          }
  
          if (!chop && Designations::isPlantMarked(plant))
          {
              if (count_only)
              {
                  if (Designations::canUnmarkPlant(plant))
                      count++;
              }
              else
              {
                  if (Designations::unmarkPlant(plant))
                      count++;
              }
          }
      }
  
      return count;
  }
  
  static bool is_valid_item(df::item *item)
  {
      for (size_t i = 0; i < item->general_refs.size(); i++)
      {
          df::general_ref *ref = item->general_refs[i];
  
          switch (ref->getType())
          {
          case general_ref_type::CONTAINED_IN_ITEM:
              return false;
  
          case general_ref_type::UNIT_HOLDER:
              return false;
  
          case general_ref_type::BUILDING_HOLDER:
              return false;
  
          default:
              break;
          }
      }
  
      for (size_t i = 0; i < item->specific_refs.size(); i++)
      {
          df::specific_ref *ref = item->specific_refs[i];
  
          if (ref->type == specific_ref_type::JOB)
          {
              // Ignore any items assigned to a job
              return false;
          }
      }
  
      return true;
  }
  
  static int get_log_count()
  {
      std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
  
      // Pre-compute a bitmask with the bad flags
      df::item_flags bad_flags;
      bad_flags.whole = 0;
  
  #define F(x) bad_flags.bits.x = true;
      F(dump); F(forbid); F(garbage_collect);
      F(hostile); F(on_fire); F(rotten); F(trader);
      F(in_building); F(construction); F(artifact);
      F(spider_web); F(owned); F(in_job);
  #undef F
  
      size_t valid_count = 0;
      for (size_t i = 0; i < items.size(); i++)
      {
          df::item *item = items[i];
  
          if (item->getType() != item_type::WOOD)
              continue;
  
          if (item->flags.whole & bad_flags.whole)
              continue;
  
          if (!is_valid_item(item))
              continue;
  
          ++valid_count;
      }
  
      return valid_count;
  }
  
  static void set_threshold_check(bool state)
  {
      wait_for_threshold = state;
      save_config();
  }
  
  static void do_autochop()
  {
      int log_count = get_log_count();
      if (wait_for_threshold)
      {
          if (log_count < min_logs)
          {
              set_threshold_check(false);
              do_chop_designation(true, false);
          }
      }
      else
      {
          if (log_count >= max_logs)
          {
              set_threshold_check(true);
              do_chop_designation(false, false);
          }
          else
          {
              do_chop_designation(true, false);
          }
      }
  }
  
  class ViewscreenAutochop : public dfhack_viewscreen
  {
  public:
      ViewscreenAutochop():
          selected_column(0),
          current_log_count(0),
          marked_tree_count(0),
          skipped_tree_count(0)
      {
          edit_mode = EDIT_NONE;
          burrows_column.multiselect = true;
          burrows_column.setTitle("Burrows");
          burrows_column.bottom_margin = 3;
          burrows_column.allow_search = false;
          burrows_column.text_clip_at = 30;
  
          populateBurrowsColumn();
          message.clear();
      }
  
      void populateBurrowsColumn()
      {
          selected_column = 0;
  
          burrows_column.clear();
  
          for (df::burrow *burrow : ui->burrows.list)
          {
              string name = burrow->name;
              if (name.empty())
                  name = "Burrow " + int_to_string(burrow->id + 1);
              auto elem = ListEntry<df::burrow *>(name, burrow);
              elem.selected = watchedBurrows.isBurrowWatched(burrow);
              burrows_column.add(elem);
          }
  
          burrows_column.fixWidth();
          burrows_column.filterDisplay();
  
          current_log_count = get_log_count();
          marked_tree_count = do_chop_designation(false, true, &skipped_tree_count);
      }
  
      void change_min_logs(int delta)
      {
          if (!autochop_enabled)
              return;
  
          min_logs += delta;
          if (min_logs < 0)
              min_logs = 0;
          if (min_logs > max_logs)
              max_logs = min_logs;
      }
  
      void change_max_logs(int delta)
      {
          if (!autochop_enabled)
              return;
  
          max_logs += delta;
          if (max_logs < min_logs)
              min_logs = max_logs;
      }
  
      void feed(set<df::interface_key> *input)
      {
          if (edit_mode != EDIT_NONE)
          {
              string entry = int_to_string(edit_mode == EDIT_MIN ? min_logs : max_logs);
              if (input->count(interface_key::LEAVESCREEN) || input->count(interface_key::SELECT))
              {
                  if (edit_mode == EDIT_MIN)
                      max_logs = std::max(min_logs, max_logs);
                  else if (edit_mode == EDIT_MAX)
                      min_logs = std::min(min_logs, max_logs);
                  edit_mode = EDIT_NONE;
              }
              else if (input->count(interface_key::STRING_A000))
              {
                  if (!entry.empty())
                      entry.erase(entry.size() - 1);
              }
              else if (entry.size() < 5)
              {
                  for (auto k = input->begin(); k != input->end(); ++k)
                  {
                      char ch = char(Screen::keyToChar(*k));
                      if (ch >= '0' && ch <= '9')
                          entry += ch;
                  }
              }
  
              switch (edit_mode)
              {
              case EDIT_MIN:
                  min_logs = string_to_int(entry);
                  break;
              case EDIT_MAX:
                  max_logs = string_to_int(entry);
                  break;
              default: break;
              }
  
              return;
          }
  
          bool key_processed = false;
          message.clear();
          switch (selected_column)
          {
          case 0:
              key_processed = burrows_column.feed(input);
              break;
          }
  
          if (key_processed)
          {
              if (input->count(interface_key::SELECT))
                  updateAutochopBurrows();
              return;
          }
  
          if (input->count(interface_key::LEAVESCREEN))
          {
              save_config();
              input->clear();
              Screen::dismiss(this);
              if (autochop_enabled)
                  do_autochop();
              return;
          }
          else if  (input->count(interface_key::CUSTOM_A))
          {
              autochop_enabled = !autochop_enabled;
          }
          else if  (input->count(interface_key::CUSTOM_D))
          {
              int count = do_chop_designation(true, false);
              message = "Trees marked for chop: " + int_to_string(count);
              marked_tree_count = do_chop_designation(false, true, &skipped_tree_count);
              if (skipped_tree_count)
              {
                  message += ", skipped: " + int_to_string(skipped_tree_count);
              }
          }
          else if  (input->count(interface_key::CUSTOM_U))
          {
              int count = do_chop_designation(false, false);
              message = "Trees unmarked: " + int_to_string(count);
              marked_tree_count = do_chop_designation(false, true, &skipped_tree_count);
              if (skipped_tree_count)
              {
                  message += ", skipped: " + int_to_string(skipped_tree_count);
              }
          }
          else if  (input->count(interface_key::CUSTOM_N))
          {
              edit_mode = EDIT_MIN;
          }
          else if  (input->count(interface_key::CUSTOM_M))
          {
              edit_mode = EDIT_MAX;
          }
          else if  (input->count(interface_key::CUSTOM_SHIFT_N))
          {
              min_logs = LOG_CAP_MAX + 1;
              max_logs = LOG_CAP_MAX + 1;
          }
          else if  (input->count(interface_key::CUSTOM_H))
          {
              change_min_logs(-1);
          }
          else if  (input->count(interface_key::CUSTOM_SHIFT_H))
          {
              change_min_logs(-10);
          }
          else if  (input->count(interface_key::CUSTOM_J))
          {
              change_min_logs(1);
          }
          else if  (input->count(interface_key::CUSTOM_SHIFT_J))
          {
              change_min_logs(10);
          }
          else if  (input->count(interface_key::CUSTOM_K))
          {
              change_max_logs(-1);
          }
          else if  (input->count(interface_key::CUSTOM_SHIFT_K))
          {
              change_max_logs(-10);
          }
          else if  (input->count(interface_key::CUSTOM_L))
          {
              change_max_logs(1);
          }
          else if  (input->count(interface_key::CUSTOM_SHIFT_L))
          {
              change_max_logs(10);
          }
          else if  (input->count(interface_key::CUSTOM_F))
          {
              skip.fruit_trees = !skip.fruit_trees;
          }
          else if  (input->count(interface_key::CUSTOM_E))
          {
              skip.food_trees = !skip.food_trees;
          }
          else if  (input->count(interface_key::CUSTOM_C))
          {
              skip.cook_trees = !skip.cook_trees;
          }
          else if (enabler->tracking_on && enabler->mouse_lbut)
          {
              if (burrows_column.setHighlightByMouse())
              {
                  selected_column = 0;
              }
  
              enabler->mouse_lbut = enabler->mouse_rbut = 0;
          }
      }
  
      void render()
      {
          if (Screen::isDismissed(this))
              return;
  
          dfhack_viewscreen::render();
  
          Screen::clear();
          Screen::drawBorder("  Autochop  ");
  
          burrows_column.display(selected_column == 0);
  
          int32_t y = gps->dimy - 3;
          int32_t x = 2;
          OutputHotkeyString(x, y, "Leave", "Esc");
          x += 3;
          OutputString(COLOR_YELLOW, x, y, message);
  
          y = 3;
          int32_t left_margin = burrows_column.getMaxItemWidth() + 3;
          x = left_margin;
          if (burrows_column.getSelectedElems().size() > 0)
          {
              OutputString(COLOR_GREEN, x, y, "Will chop in selected burrows", true, left_margin);
              ++y;
          }
          else
          {
              OutputString(COLOR_YELLOW, x, y, "Will chop from whole map", true, left_margin);
              OutputString(COLOR_YELLOW, x, y, "Select from left to chop in specific burrows", true, left_margin);
          }
  
          ++y;
  
          using namespace df::enums::interface_key;
          OutputToggleString(x, y, "Autochop", CUSTOM_A, autochop_enabled, true, left_margin);
          OutputHotkeyString(x, y, "Designate Now", CUSTOM_D, true, left_margin);
          OutputHotkeyString(x, y, "Undesignate Now", CUSTOM_U, true, left_margin);
          OutputHotkeyString(x, y, "Toggle Burrow", "Enter", true, left_margin);
          if (autochop_enabled)
          {
              const struct {
                  const char *caption;
                  int count;
                  bool in_edit;
                  df::interface_key key;
                  df::interface_key skeys[4];
              } rows[] = {
                  {"Min Logs: ", min_logs, edit_mode == EDIT_MIN, CUSTOM_N, {CUSTOM_H, CUSTOM_J, CUSTOM_SHIFT_H, CUSTOM_SHIFT_J}},
                  {"Max Logs: ", max_logs, edit_mode == EDIT_MAX, CUSTOM_M, {CUSTOM_K, CUSTOM_L, CUSTOM_SHIFT_K, CUSTOM_SHIFT_L}}
              };
              for (size_t i = 0; i < sizeof(rows) / sizeof(rows[0]); ++i)
              {
                  auto row = rows[i];
                  OutputHotkeyString(x, y, row.caption, row.key);
                  auto prev_x = x;
                  if (row.in_edit)
                      OutputString(COLOR_LIGHTCYAN, x, y, int_to_string(row.count) + "_");
                  else if (row.count <= LOG_CAP_MAX)
                      OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(row.count));
                  else
                      OutputString(COLOR_LIGHTBLUE, x, y, "Unlimited");
                  if (edit_mode == EDIT_NONE)
                  {
                      x = std::max(x, prev_x + 10);
                      for (size_t j = 0; j < sizeof(row.skeys) / sizeof(row.skeys[0]); ++j)
                          OutputString(COLOR_LIGHTGREEN, x, y, DFHack::Screen::getKeyDisplay(row.skeys[j]));
                      OutputString(COLOR_WHITE, x, y, ": Step");
                  }
                  OutputString(COLOR_WHITE, x, y, "", true, left_margin);
              }
              OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin);
              OutputToggleString(x, y, "Skip Fruit Trees", CUSTOM_F, skip.fruit_trees, true, left_margin);
              OutputToggleString(x, y, "Skip Edible Product Trees", CUSTOM_E, skip.food_trees, true, left_margin);
              OutputToggleString(x, y, "Skip Cookable Product Trees", CUSTOM_C, skip.cook_trees, true, left_margin);
          }
  
          ++y;
          OutputString(COLOR_BROWN, x, y, "Current Counts", true, left_margin);
          OutputString(COLOR_WHITE, x, y, "Current Logs: ");
          OutputString(COLOR_GREEN, x, y, int_to_string(current_log_count), true, left_margin);
          OutputString(COLOR_WHITE, x, y, "Marked Trees: ");
          OutputString(COLOR_GREEN, x, y, int_to_string(marked_tree_count), true, left_margin);
      }
  
      std::string getFocusString() { return "autochop"; }
  
      void updateAutochopBurrows()
      {
          watchedBurrows.clear();
          vector<df::burrow *> v = burrows_column.getSelectedElems();
          for_each_<df::burrow *>(v, [] (df::burrow *b) { watchedBurrows.add(b->id); });
      }
  
  private:
      ListColumn<df::burrow *> burrows_column;
      int selected_column;
      int current_log_count;
      int marked_tree_count;
      int skipped_tree_count;
      MapExtras::MapCache mcache;
      string message;
      enum { EDIT_NONE, EDIT_MIN, EDIT_MAX } edit_mode;
  
      void validateColumn()
      {
          set_to_limit(selected_column, 0);
      }
  
      void resize(int32_t x, int32_t y)
      {
          dfhack_viewscreen::resize(x, y);
          burrows_column.resize();
      }
  };
  
  struct autochop_hook : public df::viewscreen_dwarfmodest
  {
      typedef df::viewscreen_dwarfmodest interpose_base;
  
      bool isInDesignationMenu()
      {
          using namespace df::enums::ui_sidebar_mode;
          return (ui->main.mode == DesignateChopTrees);
      }
  
      void sendKey(const df::interface_key &key)
      {
          set<df::interface_key> tmp;
          tmp.insert(key);
          INTERPOSE_NEXT(feed)(&tmp);
      }
  
      DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
      {
          if (isInDesignationMenu() && input->count(interface_key::CUSTOM_C))
          {
              sendKey(interface_key::LEAVESCREEN);
              Screen::show(dts::make_unique<ViewscreenAutochop>(), plugin_self);
          }
          else
          {
              INTERPOSE_NEXT(feed)(input);
          }
      }
  
      DEFINE_VMETHOD_INTERPOSE(void, render, ())
      {
          INTERPOSE_NEXT(render)();
  
          auto dims = Gui::getDwarfmodeViewDims();
          if (dims.menu_x1 <= 0)
              return;
  
          if (!isInDesignationMenu())
              return;
  
          int left_margin = dims.menu_x1 + 1;
          int x = left_margin;
          int y = 26;
          OutputHotkeyString(x, y, "Autochop Dashboard", "c");
      }
  };
  
  IMPLEMENT_VMETHOD_INTERPOSE_PRIO(autochop_hook, feed, 100);
  IMPLEMENT_VMETHOD_INTERPOSE_PRIO(autochop_hook, render, 100);
  
  
  command_result df_autochop (color_ostream &out, vector <string> & parameters)
  {
      for (size_t i = 0; i < parameters.size(); i++)
      {
          if (parameters[i] == "help" || parameters[i] == "?")
              return CR_WRONG_USAGE;
          if (parameters[i] == "debug")
              save_config();
          else
              return CR_WRONG_USAGE;
      }
      if (Maps::IsValid())
          Screen::show(dts::make_unique<ViewscreenAutochop>(), plugin_self);
      return CR_OK;
  }
  
  DFhackCExport command_result plugin_onupdate (color_ostream &out)
  {
      if (!autochop_enabled)
          return CR_OK;
  
      if(!Maps::IsValid())
          return CR_OK;
  
      if (DFHack::World::ReadPauseState())
          return CR_OK;
  
      if (world->frame_counter % 1200 != 0) // Check every day
          return CR_OK;
  
      do_autochop();
  
      return CR_OK;
  }
  
  DFHACK_PLUGIN_IS_ENABLED(is_enabled);
  
  DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
  {
      if (!gps)
          return CR_FAILURE;
  
      if (enable != is_enabled)
      {
          if (!INTERPOSE_HOOK(autochop_hook, feed).apply(enable) ||
              !INTERPOSE_HOOK(autochop_hook, render).apply(enable))
              return CR_FAILURE;
  
          is_enabled = enable;
          initialize();
      }
  
      return CR_OK;
  }
  
  DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
  {
      commands.push_back(PluginCommand(
          "autochop", "Auto-harvest trees when low on stockpiled logs",
          df_autochop, false,
          "Opens the automated chopping control screen. Specify 'debug' to forcibly save settings.\n"
      ));
  
      initialize();
      return CR_OK;
  }
  
  DFhackCExport command_result plugin_shutdown ( color_ostream &out )
  {
      return CR_OK;
  }
  
  DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
  {
      switch (event) {
      case SC_MAP_LOADED:
          initialize();
          break;
      default:
          break;
      }
  
      return CR_OK;
  }
  