////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2012-2026 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Octave is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <algorithm>
#include <iomanip>

#include "cdef-class.h"
#include "cdef-manager.h"
#include "cdef-method.h"
#include "cdef-package.h"
#include "cdef-property.h"
#include "cdef-utils.h"
#include "errwarn.h"
#include "interpreter-private.h"
#include "interpreter.h"
#include "load-path.h"
#include "ov-builtin.h"
#include "ov-classdef.h"
#include "ov-fcn-handle.h"
#include "ov-usr-fcn.h"
#include "parse.h"
#include "pt-assign.h"
#include "pt-classdef.h"
#include "pt-eval.h"
#include "pt-idx.h"
#include "pt-misc.h"
#include "pt-stmt.h"
#include "pt-walk.h"
#include "unwind-prot.h"

#define OCTAVE_CDEF_CLASS_DEBUG 0
#if OCTAVE_CDEF_CLASS_DEBUG
#  include <iostream>
#endif

OCTAVE_BEGIN_NAMESPACE(octave)

static octave_value
make_fcn_handle (const octave_value& fcn, const std::string& meth_name,
                 const std::string& class_name)
{
  octave_value retval;

  if (fcn.is_defined ())
    {
      // FCN_HANDLE: METHOD
      octave_fcn_handle *fh
        = new octave_fcn_handle (fcn, class_name, meth_name);

      retval = octave_value (fh);
    }

  return retval;
}

cdef_class::cdef_class_rep::cdef_class_rep (const std::list<cdef_class>& superclasses)
  : cdef_meta_object_rep (), m_member_count (0), m_handle_class (false),
    m_meta (false)
{
  put ("SuperClasses", to_ov (superclasses));
  m_implicit_ctor_list = superclasses;
}

cdef_method
cdef_class::cdef_class_rep::find_method (const std::string& nm, bool local)
{
  auto it = m_method_map.find (nm);

  if (it == m_method_map.end ())
    {
      // FIXME: look into class directory
    }
  else
    {
      cdef_method& meth = it->second;

      // FIXME: check if method reload needed

      if (meth.ok ())
        return meth;
    }

  if (! local)
    {
      // Look into superclasses

      Cell super_classes = get ("SuperClasses").cell_value ();
      cdef_class cls1;
      int nfnd = 0;

      for (int i = 0; i < super_classes.numel (); i++)
        {
          cdef_class cls = lookup_class (super_classes(i));

          cdef_method meth = cls.find_method (nm);

          if (meth.ok ())
            {
              nfnd++;
              if (nfnd == 1)
                cls1 = cls;
              else if (nfnd == 2)
                // FIXME: This error is emitted when a method with conflicting
                //        definitions is attempted to be used. Ideally, this
                //        error would be emitted on construction of the object.
                error ("method %s: conflicting definitions in classes '%s' and '%s'",
                       nm.c_str (), cls1.get_name ().c_str (),
                       cls.get_name ().c_str ());
            }
        }

      if (nfnd == 1)
        return cls1.find_method (nm);
    }

  return cdef_method ();
}

class ctor_analyzer : public tree_walker
{
public:

  ctor_analyzer () = delete;

  ctor_analyzer (const std::string& ctor, const std::string& obj)
    : tree_walker (), m_who (ctor), m_obj_name (obj) { }

  OCTAVE_DISABLE_COPY_MOVE (ctor_analyzer)

  ~ctor_analyzer () = default;

  void visit_statement (tree_statement& t)
  {
    if (t.is_expression ())
      t.expression ()->accept (*this);
  }

  void visit_simple_assignment (tree_simple_assignment& t)
  {
    t.right_hand_side ()->accept (*this);
  }

  void visit_multi_assignment (tree_multi_assignment& t)
  {
    t.right_hand_side ()->accept (*this);
  }

  void visit_index_expression (tree_index_expression& t)
  {
    t.expression ()->accept (*this);
  }

  std::list<cdef_class> get_constructor_list () const
  { return m_ctor_list; }

  // NO-OP

  void visit_anon_fcn_handle (tree_anon_fcn_handle&) { }
  void visit_argument_list (tree_argument_list&) { }
  void visit_binary_expression (tree_binary_expression&) { }
  void visit_break_command (tree_break_command&) { }
  void visit_colon_expression (tree_colon_expression&) { }
  void visit_continue_command (tree_continue_command&) { }
  void visit_decl_command (tree_decl_command&) { }
  void visit_decl_init_list (tree_decl_init_list&) { }
  void visit_decl_elt (tree_decl_elt&) { }
  void visit_simple_for_command (tree_simple_for_command&) { }
  void visit_complex_for_command (tree_complex_for_command&) { }
  void visit_octave_user_script (octave_user_script&) { }
  void visit_octave_user_function (octave_user_function&) { }
  void visit_function_def (tree_function_def&) { }
  void visit_identifier (tree_identifier&) { }
  void visit_if_clause (tree_if_clause&) { }
  void visit_if_command (tree_if_command&) { }
  void visit_if_command_list (tree_if_command_list&) { }
  void visit_switch_case (tree_switch_case&) { }
  void visit_switch_case_list (tree_switch_case_list&) { }
  void visit_switch_command (tree_switch_command&) { }
  void visit_matrix (tree_matrix&) { }
  void visit_cell (tree_cell&) { }
  void visit_no_op_command (tree_no_op_command&) { }
  void visit_constant (tree_constant&) { }
  void visit_fcn_handle (tree_fcn_handle&) { }
  void visit_parameter_list (tree_parameter_list&) { }
  void visit_postfix_expression (tree_postfix_expression&) { }
  void visit_prefix_expression (tree_prefix_expression&) { }
  void visit_return_command (tree_return_command&) { }
  void visit_try_catch_command (tree_try_catch_command&) { }
  void visit_unwind_protect_command (tree_unwind_protect_command&) { }
  void visit_while_command (tree_while_command&) { }
  void visit_do_until_command (tree_do_until_command&) { }

  void visit_superclass_ref (tree_superclass_ref& t)
  {
    if (t.method_name () == m_obj_name)
      {
        std::string class_name = t.class_name ();

        cdef_class cls = lookup_class (class_name, false);

        if (cls.ok ())
          m_ctor_list.push_back (cls);
      }
  }

private:

  // The name of the constructor being analyzed.
  std::string m_who;

  // The name of the first output argument of the constructor.
  std::string m_obj_name;

  // The list of superclass constructors that are explicitly called.
  std::list<cdef_class> m_ctor_list;
};

void
cdef_class::cdef_class_rep::install_method (const cdef_method& meth)
{
  std::string method_name = meth.get_name ();
  auto it = m_method_map.find (method_name);
  //  Check if a method with the same name already exists before
  //  installing a new one.
  if (it != m_method_map.end ())
    {
      std::string class_file = file_name ();
      error ("duplicate method '%s' in class '%s' in file '%s'",
             method_name.c_str (), get_name ().c_str (), class_file.c_str ());
    }

  // Now safely install the new method
  m_method_map[method_name] = meth;

  m_member_count++;

  if (meth.is_constructor ())
    {
      // Analyze the constructor code to determine what superclass
      // constructors are called explicitly.

      octave_value ov_fcn = meth.get_function ();

      if (ov_fcn.is_defined ())
        {
          octave_user_function *uf = ov_fcn.user_function_value (true);

          if (uf)
            {
              tree_parameter_list *ret_list = uf->return_list ();
              tree_statement_list *body = uf->body ();

              if (! ret_list || ret_list->size () != 1)
                error ("%s: invalid constructor output arguments",
                       meth.get_name ().c_str ());

              std::string m_obj_name = ret_list->front ()->name ();
              ctor_analyzer a (meth.get_name (), m_obj_name);

              body->accept (a);

              std::list<cdef_class> explicit_ctor_list
                = a.get_constructor_list ();

              for (const auto& cdef_cls : explicit_ctor_list)
                {
#if OCTAVE_CDEF_CLASS_DEBUG
                  std::cerr << "explicit superclass constructor: "
                            << cdef_cls.get_name () << std::endl;
#endif

                  m_implicit_ctor_list.remove (cdef_cls);
                }
            }
        }
    }
}

void
cdef_class::cdef_class_rep::load_all_methods ()
{
  // FIXME: re-scan class directory
}

Cell
cdef_class::cdef_class_rep::get_methods (bool include_ctor)
{
  std::map<std::string, cdef_method> meths;

  find_methods (meths, false, include_ctor);

  Cell c (meths.size (), 1);

  int idx = 0;

  for (const auto& nm_mthd : meths)
    c(idx++, 0) = to_ov (nm_mthd.second);

  return c;
}

std::map<std::string, cdef_method>
cdef_class::cdef_class_rep::get_method_map (bool only_inherited,
    bool include_ctor)
{
  std::map<std::string, cdef_method> methods;

  find_methods (methods, only_inherited, include_ctor);

  return methods;
}

void
cdef_class::cdef_class_rep::find_methods (std::map<std::string,
    cdef_method>& meths,
    bool only_inherited,
    bool include_ctor)
{
  load_all_methods ();

  for (const auto& it : m_method_map)
    {
      if (include_ctor || ! it.second.is_constructor ())
        {
          std::string nm = it.second.get_name ();

          if (meths.find (nm) == meths.end ())
            {
              if (only_inherited)
                {
                  octave_value acc = it.second.get ("Access");

                  if (! acc.is_string ()
                      || acc.string_value () == "private")
                    continue;
                }

              meths[nm] = it.second;
            }
        }
    }

  // Look into superclasses

  Cell super_classes = get ("SuperClasses").cell_value ();

  for (int i = 0; i < super_classes.numel (); i++)
    {
      cdef_class cls = lookup_class (super_classes(i));

      cls.get_rep ()->find_methods (meths, true, false);
    }
}

cdef_property
cdef_class::cdef_class_rep::find_property (const std::string& nm)
{
  auto it = m_property_map.find (nm);

  if (it != m_property_map.end ())
    {
      cdef_property& prop = it->second;

      if (prop.ok ())
        return prop;
    }

  // Look into superclasses

  Cell super_classes = get ("SuperClasses").cell_value ();
  cdef_class cls1;
  int nfnd = 0;

  for (int i = 0; i < super_classes.numel (); i++)
    {
      cdef_class cls = lookup_class (super_classes(i));

      cdef_property prop = cls.find_property (nm);

      if (prop.ok ())
        {
          nfnd++;
          if (nfnd == 1)
            cls1 = cls;
          else if (nfnd == 2)
            // FIXME: This error is emitted when a property with conflicting
            //        definitions is attempted to be used. Ideally, this
            //        error would be emitted on construction of the object.
            error ("property %s: conflicting definitions in classes '%s' and '%s'",
                   nm.c_str (), cls1.get_name ().c_str (),
                   cls.get_name ().c_str ());
        }
     }

  if (nfnd == 1)
    return cls1.find_property (nm);

  return cdef_property ();
}

void
cdef_class::cdef_class_rep::install_property (const cdef_property& prop)
{
  std::string prop_name = prop.get_name ();
  auto it = m_property_map.find (prop_name);
  //  Check if a property with the same name already exists before
  //  installing a new one.
  if (it != m_property_map.end ())
    {
      std::string class_file = file_name ();
      error ("duplicate property '%s' in class '%s' in file '%s'",
             prop_name.c_str (), get_name ().c_str (), class_file.c_str ());
    }

  m_property_map[prop_name] = prop;

  // Register the insertion rank of this property
  m_property_rank_map[prop.get_name ()] = m_member_count;

  m_member_count++;
}

Cell
cdef_class::cdef_class_rep::get_properties (int mode)
{
  std::map<property_key, cdef_property> props;

  props = get_property_map (mode);

  Cell c (props.size (), 1);

  int idx = 0;

  for (const auto& pname_prop : props)
    c(idx++, 0) = to_ov (pname_prop.second);

  return c;
}

std::map<property_key, cdef_property>
cdef_class::cdef_class_rep::get_property_map (int mode)
{
  std::map<property_key, cdef_property> props;

  find_properties (props, mode);

  return props;
}

void
cdef_class::cdef_class_rep::find_properties
  (std::map<property_key, cdef_property>& props, int mode)
{
  std::set<std::string> prop_names;

  // The only reason we are introducing 'prop_names' is to keep
  // track of property names and avoid returning duplicates.
  // There is no easy way to do it based on 'props', which is going
  // to use complex keys of type 'property_key'.
  find_properties_aux (props, prop_names, mode);
}

void
cdef_class::cdef_class_rep::find_properties_aux
  (std::map<property_key, cdef_property>& props,
   std::set<std::string>& prop_names, int mode)
{
  // 'offset' starts at 0 and is incremented whenever we move
  // in the class ancestry list.
  static unsigned int property_offset {0};

  for (const auto& it : m_property_map)
    {
      std::string nm = it.second.get_name ();

      if (prop_names.find (nm) == prop_names.end ())
        {
          if (mode == property_inherited)
            {
              octave_value acc = it.second.get ("GetAccess");

              if (! acc.is_string ()
                  || acc.string_value () == "private")
                continue;
            }

          const property_key pk =
            std::make_pair (property_offset + m_property_rank_map[nm], nm);
          props[pk] = it.second;
          prop_names.insert (nm);
        }
    }

  property_offset += m_member_count;

  // Look into superclasses

  Cell super_classes = get ("SuperClasses").cell_value ();

  for (int i = 0; i < super_classes.numel (); i++)
    {
      cdef_class cls = lookup_class (super_classes(i));

      cls.get_rep ()->find_properties_aux (props, prop_names,
                                           (mode == property_all
                                            ? property_all
                                            : property_inherited));
    }

  property_offset = 0;
}

void
cdef_class::cdef_class_rep::find_names (std::set<std::string>& names,
                                        bool all)
{
  load_all_methods ();

  for (const auto& cls_fnmap : m_method_map)
    {
      if (! cls_fnmap.second.is_constructor ())
        {
          std::string nm = cls_fnmap.second.get_name ();

          if (! all)
            {
              octave_value acc = cls_fnmap.second.get ("Access");

              if (! acc.is_string()
                  || acc.string_value () != "public")
                continue;
            }

          names.insert (nm);
        }
    }

  for (const auto& pname_prop : m_property_map)
    {
      std::string nm = pname_prop.second.get_name ();

      if (! all)
        {
          octave_value acc = pname_prop.second.get ("GetAccess");

          if (! acc.is_string()
              || acc.string_value () != "public")
            continue;
        }

      names.insert (nm);
    }

  // Look into superclasses

  Cell super_classes = get ("SuperClasses").cell_value ();

  for (int i = 0; i < super_classes.numel (); i++)
    {
      cdef_class cls = lookup_class (super_classes(i));

      cls.get_rep ()->find_names (names, all);
    }
}

string_vector
cdef_class::cdef_class_rep::get_names ()
{
  std::set<std::string> names;

  find_names (names, false);

  string_vector v (names);

  return v.sort (true);
}

void
cdef_class::cdef_class_rep::delete_object (const cdef_object& obj)
{
  cdef_method dtor = find_method ("delete");

  // FIXME: would it be better to tell find_method above to not find
  // overloaded functions?

  if (dtor.ok () && dtor.is_defined_in_class (get_name ()))
    dtor.execute (obj, octave_value_list (), 0, true, "destructor");

  // FIXME: should we destroy corresponding properties here?

  // Call "delete" in super classes

  Cell super_classes = get ("SuperClasses").cell_value ();

  for (int i = 0; i < super_classes.numel (); i++)
    {
      cdef_class cls = lookup_class (super_classes(i));

      if (cls.get_name () != "handle")
        cls.delete_object (obj);
    }
}

octave_value_list
cdef_class::cdef_class_rep::meta_subsref (const std::string& type,
    const std::list<octave_value_list>& idx,
    int nargout)
{
  std::size_t skip = 1;

  octave_value_list retval;

  switch (type[0])
    {
    case '(':
      // Constructor call

#if OCTAVE_CDEF_CLASS_DEBUG
      std::cerr << "constructor" << std::endl;
#endif

      retval(0) = construct (idx.front ());
      break;

    case '.':
      {
        // Static method, constant (or property?)

#if OCTAVE_CDEF_CLASS_DEBUG
        std::cerr << "static method/property" << std::endl;
#endif

        if (idx.front ().length () != 1)
          error ("invalid meta.class indexing");

        std::string nm = idx.front ()(0).xstring_value ("invalid meta.class indexing, expected a method or property name");

        cdef_method meth = find_method (nm);

        if (meth.ok ())
          {
            if (! meth.is_static ())
              error ("method '%s' is not static", nm.c_str ());

            octave_value_list args;

            if (type.length () > 1 && idx.size () > 1 && type[1] == '(')
              {
                args = *(++(idx.begin ()));
                skip++;
              }

            retval = meth.execute (args, (type.length () > skip
                                          ? 1 : nargout), true,
                                   "meta.class");
          }
        else
          {
            cdef_property prop = find_property (nm);

            if (! prop.ok ())
              error ("no such method or property '%s'", nm.c_str ());

            if (! prop.is_constant ())
              error ("property '%s' is not constant", nm.c_str ());

            retval(0) = prop.get_value (true, "meta.class");
          }
      }
      break;

    default:
      error ("invalid meta.class indexing");
      break;
    }

  if (type.length () > skip && idx.size () > skip && ! retval.empty ())
    retval = retval(0).next_subsref (nargout, type, idx, skip);

  return retval;
}

void
cdef_class::cdef_class_rep::meta_release ()
{
  cdef_manager& cdm = __get_cdef_manager__ ();

  cdm.unregister_class (wrap ());
}

void
cdef_class::cdef_class_rep::initialize_object (cdef_object& obj)
{
  // Populate the object with default property values

  std::list<cdef_class> super_classes
    = lookup_classes (get ("SuperClasses").cell_value ());

  for (auto& cls : super_classes)
    cls.initialize_object (obj);

  for (const auto& pname_prop : m_property_map)
    {
      if (! pname_prop.second.get ("Dependent").bool_value ())
        {
          octave_value pvalue = pname_prop.second.get ("DefaultValue");

          if (pvalue.is_defined ())
            obj.put (pname_prop.first, pvalue);
          else
            obj.put (pname_prop.first, octave_value (Matrix ()));
        }
    }

  m_count++;
  obj.mark_for_construction (cdef_class (this));
}

void
cdef_class::cdef_class_rep::run_constructor (cdef_object& obj,
    const octave_value_list& args)
{
  octave_value_list empty_args;

  for (const auto& cls : m_implicit_ctor_list)
    {
      cdef_class supcls = lookup_class (cls);

      supcls.run_constructor (obj, empty_args);
    }

  std::string cls_name = get_name ();
  std::string ctor_name = get_base_name (cls_name);

  cdef_method ctor = find_method (ctor_name);

  if (ctor.ok ())
    {
      octave_value_list ctor_args (args);
      octave_value_list ctor_retval;

      ctor_args.prepend (to_ov (obj));
      ctor_retval = ctor.execute (ctor_args, 1, true, "constructor");

      if (ctor_retval.length () != 1)
        error ("%s: invalid number of output arguments for classdef constructor",
               ctor_name.c_str ());

      obj = to_cdef (ctor_retval(0));
    }

  obj.mark_as_constructed (wrap ());
}

octave_value
cdef_class::cdef_class_rep::get_method (const std::string& name) const
{
  auto p = m_method_map.find (name);

  if (p == m_method_map.end ())
    return octave_value ();

  return p->second.get_function ();
}

octave_value
cdef_class::cdef_class_rep::get_method (int line) const
{
  octave_value closest_match;
  int closest_match_end_line = std::numeric_limits<int>::max ();
  // Since we have a dynamic cast, performance could be an issue if this is
  // called from a critical path.  If performance is an issue, we can cache
  // an ordered version of the method map
  for (auto i = m_method_map.cbegin (); i != m_method_map.cend (); ++i)
    {
      const octave_value& fcn = i->second.get_function ();
      octave_user_code *user_code = fcn.user_code_value ();

      if (! user_code)
        continue;

      octave_user_function *pfcn
        = dynamic_cast<octave_user_function *> (user_code);

      if (! pfcn)
        continue;

      octave::filepos end_pos = pfcn->end_pos ();

      int end_line = end_pos.line ();

      if (line <= end_line && end_line <= closest_match_end_line
          && pfcn->is_defined () && pfcn->is_user_code ())
        {
          closest_match = fcn;
          closest_match_end_line = end_line;
        }
    }

  // repeat the same for set and get methods of properties
  for (auto i = m_property_map.cbegin (); i != m_property_map.cend (); ++i)
    {
      const octave_value& get_meth = i->second.get ("GetMethod");
      if (get_meth.is_function_handle ())
        {
          octave_user_function *pfcn
            = get_meth.user_function_value ();

          if (! pfcn)
            continue;

          octave::filepos end_pos = pfcn->end_pos ();

          int end_line = end_pos.line ();

          if (line <= end_line && end_line <= closest_match_end_line
              && pfcn->is_defined () && pfcn->is_user_code ())
            {
              closest_match = get_meth;
              closest_match_end_line = end_line;
            }
        }

      const octave_value& set_meth = i->second.get ("SetMethod");
      if (set_meth.is_function_handle ())
        {
          octave_user_function *pfcn
            = set_meth.user_function_value ();

          if (! pfcn)
            continue;

          octave::filepos end_pos = pfcn->end_pos ();

          int end_line = end_pos.line ();

          if (line <= end_line && end_line <= closest_match_end_line
              && pfcn->is_defined () && pfcn->is_user_code ())
            {
              closest_match = set_meth;
              closest_match_end_line = end_line;
            }
        }
    }

  return closest_match;
}

octave_value
cdef_class::cdef_class_rep::construct (const octave_value_list& args,
                                       const bool default_initialize)
{
  cdef_object obj = construct_object (args, default_initialize);

  if (obj.ok ())
    return to_ov (obj);

  return octave_value ();
}

cdef_object
cdef_class::cdef_class_rep::construct_object (const octave_value_list& args,
                                              const bool default_initialize)
{
  if (is_abstract ())
    error ("cannot instantiate object for abstract class '%s'",
           get_name ().c_str ());

  cdef_object obj;

  if (is_meta_class ())
    {
      // This code path is only used to create empty meta objects
      // as filler for the empty values within a meta object array.

      cdef_class this_cls = wrap ();

      static cdef_object empty_class;

      cdef_manager& cdm = __get_cdef_manager__ ();

      if (this_cls == cdm.meta_class ())
        {
          if (! empty_class.ok ())
            empty_class = cdm.make_class ("", std::list<cdef_class> ());
          obj = empty_class;
        }
      else if (this_cls == cdm.meta_property ())
        {
          static cdef_property empty_property;

          if (! empty_class.ok ())
            empty_class = cdm.make_class ("", std::list<cdef_class> ());
          if (! empty_property.ok ())
            empty_property = cdm.make_property (empty_class, "");
          obj = empty_property;
        }
      else if (this_cls == cdm.meta_method ())
        {
          static cdef_method empty_method;

          if (! empty_class.ok ())
            empty_class = cdm.make_class ("", std::list<cdef_class> ());
          if (! empty_method.ok ())
            empty_method = cdm.make_method (empty_class, "", octave_value ());
          obj = empty_method;
        }
      else if (this_cls == cdm.meta_package ())
        {
          static cdef_package empty_package;

          if (! empty_package.ok ())
            empty_package = cdm.make_package ("");
          obj = empty_package;
        }
      else
        error ("expecting meta class, property, method, or package in cdef_class::cdef_class_rep::construct_object - please report this bug");

      return obj;
    }
  else
    {
      if (is_handle_class ())
        obj = cdef_object (new handle_cdef_object ());
      else
        obj = cdef_object (new value_cdef_object ());
      obj.set_class (wrap ());

      initialize_object (obj);

      if (! default_initialize)
        run_constructor (obj, args);

      return obj;
    }

  return cdef_object ();
}

static octave_value
compute_attribute_value (tree_evaluator& tw,
                         tree_classdef_attribute *t)
{
  tree_expression *expr = t->expression ();

  if (expr)
    {
      if (expr->is_identifier ())
        {
          std::string s = expr->name ();

          if (s == "public")
            return std::string ("public");
          else if (s == "protected")
            return std::string ("protected");
          else if (s == "private")
            return std::string ("private");
        }

      return expr->evaluate (tw);
    }
  else
    return octave_value (true);
}

template <typename T>
static std::string
attribute_value_to_string (T *t, octave_value v)
{
  if (v.is_string ())
    return v.string_value ();
  else if (t->expression ())
    return t->expression ()->original_text ();
  else
    return "true";
}

cdef_class
cdef_class::make_meta_class (interpreter& interp,
                             tree_classdef *t, bool is_at_folder)
{
  cdef_class retval;

  // Class creation

  std::string class_name = t->ident ()->name ();
  std::string full_class_name = class_name;
  if (! t->package_name ().empty ())
    full_class_name = t->package_name () + '.' + full_class_name;

#if OCTAVE_CDEF_CLASS_DEBUG
  std::cerr << "class: " << full_class_name << std::endl;
#endif

  // Push a dummy scope frame on the call stack that corresponds to
  // the scope that was used when parsing classdef object.  Without
  // this, we may pick up stray values from the current scope when
  // evaluating expressions found in things like attribute lists.

  tree_evaluator& tw = interp.get_evaluator ();

  tw.push_dummy_scope (full_class_name);
  unwind_action pop_scope (&tree_evaluator::pop_scope, &tw);

  std::list<cdef_class> slist;

  if (t->superclass_list ())
    {
      for (auto& scls : (*t->superclass_list ()))
        {
          std::string sclass_name = (scls)->class_name ();

#if OCTAVE_CDEF_CLASS_DEBUG
          std::cerr << "superclass: " << sclass_name << std::endl;
#endif

          cdef_class sclass = lookup_class (sclass_name);

          if (sclass.get ("Sealed").bool_value ())
            error ("'%s' cannot inherit from '%s', because it is sealed",
                   full_class_name.c_str (), sclass_name.c_str ());

          slist.push_back (sclass);
        }
    }

  cdef_manager& cdm = __get_cdef_manager__ ();

  retval = cdm.make_class (full_class_name, slist);

  retval.doc_string (t->doc_string ());
  retval.file_name (t->file_name ());

  // Package owning this class

  if (! t->package_name ().empty ())
    {
      cdef_package pack = cdm.find_package (t->package_name ());

      if (pack.ok ())
        retval.put ("ContainingPackage", to_ov (pack));
    }

  // FIXME: instead of attaching attributes here, pass them to
  // cdef_manager::make_method.  The classdef manager contains a meta
  // object with a list of all valid properties that can be used to
  // validate the attribute list (see bug #60593).

  // Class attributes

  if (t->attribute_list ())
    {
      for (const auto& attr : (*t->attribute_list ()))
        {
          std::string aname = attr->ident ()->name ();
          octave_value avalue = compute_attribute_value (tw, attr);

#if OCTAVE_CDEF_CLASS_DEBUG
          std::cerr << "class attribute: " << aname << " = "
                    << attribute_value_to_string (attr, avalue) << std::endl;
#endif

          retval.put (aname, avalue);
        }
    }

  tree_classdef_body *b = t->body ();

  if (b)
    {
      // Keep track of the get/set accessor methods.  They will be used
      // later on when creating properties.

      std::map<std::string, octave_value> get_methods;
      std::map<std::string, octave_value> set_methods;

      // Method blocks

      std::list<tree_classdef_methods_block *> mb_list = b->method_list ();

      load_path& lp = interp.get_load_path ();

      for (auto& mb_p : mb_list)
        {
          std::map<std::string, octave_value> amap;

#if OCTAVE_CDEF_CLASS_DEBUG
          std::cerr << "method block" << std::endl;
#endif

          // Method attributes

          if (mb_p->attribute_list ())
            {
              for (auto& attr_p : *mb_p->attribute_list ())
                {
                  std::string aname = attr_p->ident ()->name ();
                  octave_value avalue = compute_attribute_value (tw, attr_p);

#if OCTAVE_CDEF_CLASS_DEBUG
                  std::cerr << "method attribute: " << aname << " = "
                            << attribute_value_to_string (attr_p, avalue)
                            << std::endl;
#endif

                  amap[aname] = avalue;
                }
            }

          // Methods

          if (mb_p->element_list ())
            {
              for (auto& mtd : *mb_p->element_list ())
                {
                  std::string mname = mtd.function_value ()->name ();
                  std::string mprefix = mname.substr (0, 4);

                  if (mprefix == "get.")
                    get_methods[mname.substr (4)]
                      = make_fcn_handle (mtd, mname, full_class_name);
                  else if (mprefix == "set.")
                    set_methods[mname.substr (4)]
                      = make_fcn_handle (mtd, mname, full_class_name);
                  else
                    {
                      cdef_method meth = cdm.make_method (retval, mname, mtd);

#if OCTAVE_CDEF_CLASS_DEBUG
                      std::cerr << (mname == class_name ? "constructor"
                                                        : "method")
                                << ": " << mname << std::endl;
#endif

                      // FIXME: instead of attaching attributes here,
                      // pass them to cdef_manager::make_method.  The
                      // classdef manager contains a meta object with
                      // a list of all valid properties that can be
                      // used to validate the attribute list (see bug
                      // #60593).

                      for (auto& attrnm_val : amap)
                        meth.put (attrnm_val.first, attrnm_val.second);

                      retval.install_method (meth);
                    }
                }
            }
        }

      if (is_at_folder)
        {
          // Look for all external methods visible on octave path at the
          // time of loading of the class.
          //
          // FIXME: This is an "extension" to Matlab behavior, which only
          // looks in the @-folder containing the original classdef file.
          // However, this is easier to implement it that way at the moment.

          std::list<std::string> external_methods
            = lp.methods (full_class_name);

          for (const auto& mtdnm : external_methods)
            {
              // FIXME: should we issue a warning if the method is already
              // defined in the classdef file?

              if (mtdnm != class_name
                  && ! retval.find_method (mtdnm, true).ok ())
                {
                  // Create a dummy method that is used until the actual
                  // method is loaded.
                  octave_user_function *fcn = new octave_user_function ();

                  fcn->stash_function_name (mtdnm);

                  cdef_method meth
                    = cdm.make_method (retval, mtdnm, octave_value (fcn));

                  retval.install_method (meth);
                }
            }
        }

      // Property blocks

      // FIXME: default property expression should be able to call static
      //        methods of the class being constructed.  A restricted
      //        CLASSNAME symbol should be added to the scope before
      //        evaluating default value expressions.

      std::list<tree_classdef_properties_block *> pb_list
        = b->property_list ();

      for (auto& pb_p : pb_list)
        {
          std::map<std::string, octave_value> amap;

#if OCTAVE_CDEF_CLASS_DEBUG
          std::cerr << "property block" << std::endl;
#endif

          // Property attributes

          if (pb_p->attribute_list ())
            {
              for (auto& attr_p : *pb_p->attribute_list ())
                {
                  std::string aname = attr_p->ident ()->name ();
                  octave_value avalue = compute_attribute_value (tw, attr_p);

#if OCTAVE_CDEF_CLASS_DEBUG
                  std::cerr << "property attribute: " << aname << " = "
                            << attribute_value_to_string (attr_p, avalue)
                            << std::endl;
#endif

                  if (aname == "Access")
                    {
                      amap["GetAccess"] = avalue;
                      amap["SetAccess"] = avalue;
                    }
                  else
                    amap[aname] = avalue;
                }
            }

          // Properties

          if (pb_p->element_list ())
            {
              for (auto& prop_p : *pb_p->element_list ())
                {
                  std::string prop_name = prop_p->ident ()->name ();

                  cdef_property prop = cdm.make_property (retval, prop_name);

                  prop.doc_string (prop_p->doc_string ());

#if OCTAVE_CDEF_CLASS_DEBUG
                  std::cerr << "property: " << prop_p->ident ()->name ()
                            << std::endl;
#endif

                  tree_expression *expr = prop_p->expression ();
                  if (expr)
                    {
                      octave_value pvalue = expr->evaluate (tw);

#if OCTAVE_CDEF_CLASS_DEBUG
                      std::cerr << "property default: "
                                << attribute_value_to_string (prop_p, pvalue)
                                << std::endl;
#endif

                      prop.put ("DefaultValue", pvalue);
                    }

                  // FIXME: instead of attaching attributes here, pass
                  // them to cdef_manager::make_property.  The
                  // classdef manager contains a meta object with a
                  // list of all valid properties that can be used to
                  // validate the attribute list (see bug #60593).

                  // Install property attributes.  This is done before
                  // assigning the property accessors so we can do validation
                  // by using cdef_property methods.

                  for (auto& attrnm_val : amap)
                    prop.put (attrnm_val.first, attrnm_val.second);

                  // Install property access methods, if any.  Remove the
                  // accessor methods from the temporary storage map, so we
                  // can detect which ones are invalid and do not correspond
                  // to a defined property.

                  auto git = get_methods.find (prop_name);

                  if (git != get_methods.end ())
                    {
                      make_function_of_class (retval, git->second);
                      prop.put ("GetMethod", git->second);
                      get_methods.erase (git);
                    }

                  auto sit = set_methods.find (prop_name);

                  if (sit != set_methods.end ())
                    {
                      make_function_of_class (retval, sit->second);
                      prop.put ("SetMethod", sit->second);
                      set_methods.erase (sit);
                    }

                  retval.install_property (prop);
                }
            }
        }
    }

  return retval;
}

octave_value
cdef_class::get_method_function (const std::string& /* nm */)
{
  return octave_value (new octave_classdef_meta (*this));
}

OCTAVE_END_NAMESPACE(octave)
