--
-- Metadata for generating data entry forms
--
-- Based on CMS form metadata code.
--
-- The core datamodel allows applications to store metadata to generate html
-- forms for use with dynamic object types.
--
-- Widget templates are provided to allow common widget / datatype combinations
-- to be named and offered in user interfaces that create object types.  
-- Default widgets identify which widget templates to use for default object
-- type forms.

create sequence t_dtype_seq start 500;
create view dtype_seq as select nextval('t_dtype_seq') as nextval;

---------------------------------------------
-- Core reference datamodel
---------------------------------------------

create table dtype_db_datatypes (
  datatype         varchar(50)
                   constraint dtype_wdgt_tmpl_dtype_fk
                   references acs_datatypes,
  db_type          varchar(50)
);

create table dtype_widgets (
  widget           varchar(100)
                   constraint dtype_widgets_pk
                   primary key
);

comment on table dtype_widgets is '
  Canonical list of all widgets defined in the system.
';

create table dtype_widget_params (
  param_id         integer
                   constraint dtype_widget_params_pk
                   primary key,
  widget           varchar(100)
                   constraint dtype_widget_params_fk
                   references dtype_widgets,
  param            varchar(100)
                   constraint dtype_widget_param_nil
                   not null,
  is_required      boolean,
  is_html          boolean,
  default_value    varchar(1000)
);

comment on table dtype_widget_params is '
  Parameters that are specific to a particular type of form widget.
';


---------------------------------------------
-- Widget templates datamodel
---------------------------------------------

create table dtype_widget_templates (
  template_id      integer
                   constraint dtype_wdgt_tmpl_tid_fk
                   references acs_objects(object_id)
                   constraint dtype_wdgt_tmpl_tid_pk
                   primary key,
  name             varchar(100)
                   constraint dtype_wdgt_tmpl_unq
                   unique,
  pretty_name      varchar(1000)
                   constraint dtype_wdgt_tmpl_pname_nn
                   not null,
  datatype         varchar(50)
                   constraint dtype_wdgt_tmpl_dtype_fk
                   references acs_datatypes,
  widget           varchar(100)
                   constraint dtype_wdgt_tmpl_wgt_fk
                   references dtype_widgets
);

comment on table dtype_widget_templates is '
  A list of common datatype / widget combinations and what to call them.
';

select acs_object_type__create_type (
    'dtype_widget_template',        -- object_type
    'Widget Template',              -- pretty_name
    'Widget Templates',             -- pretty_plural
    'acs_object',                   -- supertype
    'dtype_widget_templates',       -- table_name
    'template_id',                  -- id_column
    null,                           -- package_name
    'f',                            -- abstract_p
    null,                           -- type_extension_table
    'widget_template__title'        -- name_method
);

select acs_attribute__create_attribute (
   'dtype_widget_template',         -- object_type
   'name',                          -- attribute_name
   'text',                          -- datatype
   'Short Name',                    -- pretty_name
   'Short Names',                   -- pretty_plural
   null,                            -- table_name
   null,                            -- column_name
   null,                            -- default_value
   1,                               -- min_n_values
   1,                               -- max_n_values
   2,                               -- sort_order
   'type_specific',                 -- storage
   'f'                              -- static_p
);

select acs_attribute__create_attribute (
   'dtype_widget_template',         -- object_type
   'pretty_name',                   -- attribute_name
   'text',                          -- datatype
   'Name',                          -- pretty_name
   'Names',                         -- pretty_plural
   null,                            -- table_name
   null,                            -- column_name
   null,                            -- default_value
   1,                               -- min_n_values
   1,                               -- max_n_values
   1,                               -- sort_order
   'type_specific',                 -- storage
   'f'                              -- static_p
);

select acs_attribute__create_attribute (
   'dtype_widget_template',         -- object_type
   'datatype',                      -- attribute_name
   'text',                          -- datatype
   'Datatype',                      -- pretty_name
   'Datatypes',                     -- pretty_plural
   null,                            -- table_name
   null,                            -- column_name
   'text',                          -- default_value
   1,                               -- min_n_values
   1,                               -- max_n_values
   3,                               -- sort_order
   'type_specific',                 -- storage
   'f'                              -- static_p
);

select acs_attribute__create_attribute (
   'dtype_widget_template',         -- object_type
   'widget',                        -- attribute_name
   'text',                          -- datatype
   'Widget',                        -- pretty_name
   'Widgets',                       -- pretty_plural
   null,                            -- table_name
   null,                            -- column_name
   'text',                          -- default_value
   1,                               -- min_n_values
   1,                               -- max_n_values
   4,                               -- sort_order
   'type_specific',                 -- storage
   'f'                              -- static_p
);

create table dtype_widget_template_params (
  template_id      integer
                   constraint dtype_wgt_tpls_tpl_id_fk
                   references dtype_widget_templates,
  param_id         integer
                   constraint dtype_wgt_tpls_pm_id_fk
                   references dtype_widget_params,
  param_type       varchar(100) default 'onevalue'
                   constraint dtype_wgt_tpls_pm_type_nn
                   not null
                   constraint dtype_wgt_tpls_pm_type_ck
                   check (param_type in ('onevalue', 'onelist', 'multilist')),
  param_source     varchar(100) default 'literal'
                   constraint dtype_wgt_tpls_pm_src_nn
                   not null
                   constraint dtype_wgt_tpls_pm_src_ck 
                   check (param_source in ('literal', 'query', 'eval')),
  value		         text,
  constraint dtype_wgt_tpls_pm_pk
  primary key(template_id, param_id)
);
  
comment on table dtype_widget_template_params is '
  The parameters to apply to the templated widget for a given datatype.
';

create table dtype_default_widgets (
  template_id      integer
                   constraint dtype_wgt_tpls_tpl_id_fk
                   references dtype_widget_templates,
  datatype         varchar(50)
                   constraint dtype_dft_wgts_dtype_fk
                   references acs_datatypes
                   constraint dtype_dft_wgts_dtype_unq
                   unique
);

comment on table dtype_default_widgets is '
  Widgets to be used for particular datatypes by default.
';


---------------------------------------------
-- Form instance datamodel
---------------------------------------------

create table dtype_forms (
    form_id       integer
                  constraint dtype_form_id_pk
                  primary key,
    name          varchar(100),
    object_type   varchar(1000)
                  constraint dtype_obj_type_ref
                  references acs_object_types,
    constraint dtype_name_obj_type_unq unique(name,object_type)
);

comment on table dtype_forms is '
  Canonical list of all forms defined for each object type.
';

create table dtype_form_elements (
  element_id       integer
                   constraint dtype_felements_pk
                   primary key,
  attribute_id     integer
                   constraint dtype_felements_attr_fk
                   references acs_attributes
                   on delete cascade,
  form_id          integer
                   constraint dtype_felements_form_id_fk
                   references dtype_forms,
  widget	         varchar(100)
                   constraint dtype_felements_widget_fk
                   references dtype_widgets
                   constraint dtype_felements_widget_nil
                   not null,
  is_required      boolean
);

comment on table dtype_form_elements is '
  A map of attribute to widget for a particular object type / form.
';

create table dtype_element_params (
  element_id       integer
                   constraint dtype_element_param_elm_fk
                   references dtype_form_elements
                   on delete cascade,
  param_id         integer
                   constraint dtype_element_param_fk
                   references dtype_widget_params,
  param_type       varchar(100) default 'onevalue'
                   constraint dtype_element_param_type_nil
                   not null
                   constraint dtype_element_param_type_ck
                   check (param_type in ('onevalue', 'onelist', 'multilist')),
  param_source     varchar(100) default 'literal'
                   constraint dtype_element_param_src_nil
                   not null,
                   constraint dtype_element_param_src_ck 
                   check (param_source in ('literal', 'query', 'eval')),
  value		         text,
  constraint dtype_element_param_pk
  primary key(element_id, param_id)
);

comment on table dtype_element_params is '
  Parameter values for specific object type form widgets.
';

---------------------------------------------
-- Consolidated views
---------------------------------------------

-- This view contains the elements of all defined forms plus the elements of a
-- 'implicit' form for all object_types that have metadata.

create view dtype_form_elements_all as
  select a.attribute_id,
         a.object_type,
         a.table_name,
         a.attribute_name,
         a.pretty_name,
         a.pretty_plural,
         a.sort_order,
         a.datatype,
         a.default_value,
         a.min_n_values,
         a.max_n_values,
         a.storage,
         a.static_p,
         a.column_name,
         f.form_id,
         f.name as form_name,
         e.element_id,
         e.widget,
         e.is_required
    from acs_attributes a,
         dtype_form_elements e,
         dtype_forms f
   where a.attribute_id = e.attribute_id
     and e.form_id = f.form_id
     and a.object_type = f.object_type
  union
  select a.attribute_id,
         a.object_type,
         a.table_name,
         a.attribute_name,
         a.pretty_name,
         a.pretty_plural,
         a.sort_order,
         a.datatype,
         a.default_value,
         a.min_n_values,
         a.max_n_values,
         a.storage,
         a.static_p,
         a.column_name,
         null as form_id,
         'implicit' as form_name,
         null as element_id,
         wt.widget,
         (case when a.min_n_values > 0 then TRUE else FALSE end) as is_required
    from acs_attributes a,
         dtype_widget_templates wt,
         dtype_default_widgets dw
   where a.datatype = dw.datatype
     and dw.template_id = wt.template_id
     and dw.datatype = wt.datatype;
                      
-- This view contains all defined element parameters, any default parameters 
-- each element has by virtue of its datatype and any default parameters each
-- element has by virtue of its widget type in order of precedence.  It 
-- includes parameters for the 'implicit' forms defined in the view above.

create view dtype_element_params_all as
  select e.element_id,
         e.attribute_id,
         e.form_id,
         f.name as form_name,
         ep.param_id,
         ep.param_type,
         ep.param_source,
         ep.value,
         wp.widget,
         wp.param,
         wp.is_required,
         wp.is_html,
         wp.default_value     
    from dtype_widget_params wp,
         dtype_element_params ep,
         dtype_form_elements e,
         dtype_forms f
   where ep.param_id = wp.param_id
     and ep.element_id = e.element_id
     and e.form_id = f.form_id
   union
  select ea.element_id,
         ea.attribute_id,
         ea.form_id,
         ea.form_name,
         wtp.param_id,
         wtp.param_type,
         wtp.param_source,
         wtp.value,
         wp.widget,
         wp.param,
         wp.is_required,
         wp.is_html,
         wp.default_value
    from dtype_default_widgets dw,
         dtype_widget_params wp,
         dtype_widget_templates wt,
         dtype_widget_template_params wtp,
         dtype_form_elements_all ea
   where ea.datatype = dw.datatype
     and dw.template_id = wt.template_id
     and dw.datatype = wt.datatype
     and wt.template_id = wtp.template_id
     and wtp.param_id = wp.param_id
     and not exists (select 1
                       from dtype_element_params ep
                      where ep.element_id = ea.element_id
                        and ep.param_id = wp.param_id)
   union
  select ea.element_id,
         ea.attribute_id,
         ea.form_id,
         ea.form_name,
         wp.param_id,
         'onevalue' as param_type,
         'literal' as param_source,
         wp.default_value as value,
         wp.widget,
         wp.param,
         wp.is_required,
         wp.is_html,
         wp.default_value
    from dtype_widget_params wp,
         dtype_form_elements_all ea
   where ea.widget = wp.widget
     and not exists (select 1
                       from dtype_element_params ep
                      where ep.element_id = ea.element_id
                        and ep.param_id = wp.param_id)
     and not exists (select 1
                       from dtype_widget_template_params wtp,
                            dtype_widget_templates wt,
                            dtype_default_widgets dw
                      where dw.datatype = ea.datatype
                        and dw.template_id = wt.template_id
                        and dw.datatype = wt.datatype
                        and wt.template_id = wtp.template_id
                        and wtp.param_id = wp.param_id);


---------------------------------------------
-- Package definition
---------------------------------------------

create or replace function dtype_widget__register_form_widget (varchar,varchar,varchar,varchar,boolean,boolean)
returns integer as '
declare
  p_object_type         alias for $1;  
  p_form_name           alias for $2;  
  p_attribute_name      alias for $3;  
  p_widget              alias for $4;  
  p_is_required         alias for $5;  -- default ''f''
  p_create_form         alias for $6;
  v_form_id             dtype_forms.form_id%TYPE;
  v_attr_id             acs_attributes.attribute_id%TYPE;
  v_prev_elm            integer;       
begin
  
    -- Look for the attribute
    select attribute_id into v_attr_id 
      from acs_attributes
     where attribute_name = p_attribute_name
       and object_type = p_object_type;

    if NOT FOUND then
        if p_object_type <> ''acs_object'' and p_attribute_name <> ''object_id'' then
            raise EXCEPTION ''-20000: Attribute %: % does not exist in dtype_widget.register_form_widget'', p_object_type, p_attribute_name;
        end if;
    end if;

    -- Look for the form
    select form_id into v_form_id
      from dtype_forms
     where object_type = p_object_type
       and name = p_form_name;

    if NOT FOUND then
        if p_create_form then
            select nextval into v_form_id from dtype_seq;

            insert into dtype_forms (form_id, name, object_type)
            values (v_form_id, p_form_name, p_object_type);
        else
            raise EXCEPTION ''-20000: Form % does not exist for object type % in dtype_widget__register_form_widget'', p_form_name, p_object_type;
        end if;
    end if;

    -- Determine if a previous value exists
    select element_id into v_prev_elm
      from dtype_form_elements
     where attribute_id = v_attr_id
       and form_id = v_form_id;

    if NOT FOUND then 
      -- No previous widget registered
      -- Insert a new row 

      insert into dtype_form_elements (
          element_id,
          attribute_id, 
          form_id,
          widget, 
          is_required
      )
      select nextval,
             v_attr_id, 
             v_form_id,
             p_widget, 
             p_is_required
        from dtype_seq;

    else
      -- Old widget exists: erase parameters, update widget

      delete from dtype_element_params 
      where element_id = v_prev_elm
        and param_id in (select param_id 
                           from dtype_widget_params
                          where widget = p_widget);

      update dtype_form_elements 
         set widget = p_widget,
             is_required = p_is_required
       where attribute_id = v_attr_id
         and form_id = v_form_id;
    end if;

    return 0; 
end;' language 'plpgsql';


-- procedure set_attribute_order
create or replace function dtype_widget__set_attribute_order (varchar,varchar,integer)
returns integer as '
declare
  p_object_type         alias for $1;  
  p_attribute_name      alias for $2;  
  p_sort_order          alias for $3;  
begin

    update acs_attributes    
       set sort_order = p_sort_order
     where object_type = p_object_type
       and attribute_name = p_attribute_name;
        
    return 0; 
end;' language 'plpgsql';


-- procedure unregister_form_widget
create or replace function dtype_widget__unregister_form_widget (varchar,varchar,varchar,boolean)
returns integer as '
declare
  p_object_type         alias for $1;  
  p_form_name           alias for $2;  
  p_attribute_name      alias for $3;  
  p_delete_form         alias for $4;  -- default ''t''
  v_attr_id             acs_attributes.attribute_id%TYPE;
  v_form_id             dtype_forms.form_id%TYPE;
  v_element_id          dtype_form_elements.element_id%TYPE;
  v_element_count       integer;
  v_widget              dtype_widgets.widget%TYPE;
begin
  
    -- Look for the attribute
    
    select attribute_id into v_attr_id from acs_attributes
     where attribute_name = p_attribute_name
       and object_type = p_object_type;

    if NOT FOUND then
        raise EXCEPTION ''-20000: Attribute %: % does not exist in dtype_widget.unregister_form_widget'', p_object_type, p_attribute_name;
    end if;   

    -- Look for the widget; if no widget is registered, just return
    
    select e.element_id, e.widget into v_element_id, v_widget 
      from dtype_form_elements e,
           dtype_forms f
     where e.attribute_id = v_attr_id
       and e.form_id = f.form_id
       and f.name = p_form_name;

    if NOT FOUND then
       return null;
    end if;
  
    -- Delete the param values and the widget assignment
    delete from dtype_element_params where element_id = v_element_id;
    delete from dtype_form_elements where element_id = v_element_id;

    if p_delete_form then
        select count(*) into v_element_count
          from dtype_form_elements 
         where form_id = v_form_id;

        if v_element_count = 0 then
            delete from dtype_forms where form_id = v_form_id;
        end if;
    end if;

    return 0; 
end;' language 'plpgsql';


create or replace function dtype_widget__set_param_value (varchar,varchar,varchar,varchar,varchar,varchar,varchar)
returns integer as '
declare
  p_object_type         alias for $1;  
  p_form_name           alias for $2;  
  p_attribute_name      alias for $3;  
  p_param               alias for $4;  
  p_value               alias for $5;  
  p_param_type          alias for $6;  -- default ''one_value''
  p_param_source        alias for $7;  -- default ''literal''
  v_element_id          dtype_form_elements.element_id%TYPE;
  v_attr_id             acs_attributes.attribute_id%TYPE;
  v_form_id             dtype_forms.form_id%TYPE;
  v_widget              dtype_widgets.widget%TYPE;
  v_param_id            dtype_widget_params.param_id%TYPE;
  v_prev_value          integer;       
begin

    -- Get the attribute id and the widget 
    select e.attribute_id, 
           e.widget, 
           e.form_id,
           e.element_id 
      into v_attr_id, 
           v_widget, 
           v_form_id,
           v_element_id 
      from acs_attributes a, 
           dtype_form_elements e,
           dtype_forms f
     where a.attribute_name = p_attribute_name
       and a.object_type = p_object_type
       and a.attribute_id = e.attribute_id
       and e.form_id = f.form_id
       and f.object_type = p_object_type
       and f.name = p_form_name;

    if NOT FOUND then
      raise EXCEPTION ''-20000: No widget is registered for attribute %.% with a form % in dtype_widget__set_param_value'', p_object_type, p_attribute_name, p_form_name;
    end if;

    -- Get the param id
    select param_id into v_param_id from dtype_widget_params
     where widget = v_widget 
       and param = p_param;

    if NOT FOUND then
      raise EXCEPTION ''-20000: No parameter named % exists for the widget % in dtype_widget__set_param_value'', p_param, v_widget;
    end if;  

    -- Check if an old value exists
    -- Determine if a previous value exists
    select count(1) into v_prev_value from dual 
      where exists (select 1 from dtype_element_params
                    where element_id = v_element_id
                    and param_id = v_param_id);
    
    if v_prev_value > 0 then
      -- Update the value
      update dtype_element_params set
        param_type = p_param_type,
        param_source = p_param_source,
        value = p_value
      where
        element_id = v_element_id
      and
        param_id = v_param_id;
    else
      -- Insert a new value
      insert into dtype_element_params
        (element_id, param_id, param_type, param_source, value)
      values
        (v_element_id, v_param_id, p_param_type, p_param_source, p_value);
    end if;

    return 0; 
end;' language 'plpgsql';


create or replace function dtype_widget__set_param_value (varchar,varchar,varchar,varchar,integer,varchar,varchar)
returns integer as '
begin
    return dtype_widget__set_param_value($1, $2, $3, $4, cast ($5 as varchar), $6, $7); 
end;' language 'plpgsql';


create or replace function dtype_wdgt_tmpl__new (integer,integer,varchar,varchar,varchar,varchar,integer,varchar)
returns integer as '
declare
  p_template_id         alias for $1;  
  p_package_id          alias for $2;
  p_name                alias for $3;  
  p_pretty_name         alias for $4;  
  p_widget              alias for $5;  
  p_datatype            alias for $6;  
  p_creation_user       alias for $7;
  p_creation_ip         alias for $8;
  v_template_id         integer;
begin
  
    v_template_id := acs_object__new (
        p_template_id,
        ''dtype_widget_template'',
        current_timestamp,
        p_creation_user,
        p_creation_ip,
        p_package_id
    );

    insert into dtype_widget_templates (
        template_id,
        name,
        pretty_name,
        datatype,
        widget
    ) values (
        v_template_id,
        p_name,
        p_pretty_name,
        p_datatype,
        p_widget
    );

    return v_template_id; 
end;' language 'plpgsql';

create or replace function dtype_wdgt_tmpl__delete (integer)
returns integer as '
declare
    p_template_id alias for $1;
begin
    -- delete parameters associated with this widget template
    delete from dtype_widget_template_params
    where template_id = p_template_id;

    delete from dtype_widget_templates
    where template_id = p_template_id;
    PERFORM acs_object__delete(p_template_id);
    return 0;
end;
' language 'plpgsql';

create or replace function dtype_wdgt_tmpl__set_param_value (varchar,varchar,varchar,varchar,varchar,varchar,varchar)
returns integer as '
declare
  p_template_name       alias for $1;  
  p_param               alias for $2;  
  p_value               alias for $3;  
  p_param_type          alias for $4;  -- default ''one_value''
  p_param_source        alias for $5;  -- default ''literal''
  v_template_id         dtype_widget_templates.template_id%TYPE;
  v_param_id            dtype_widget_params.param_id%TYPE;
  v_widget              dtype_widgets.widget%TYPE;
  v_prev_value          integer;       
begin

    -- Get the template id and the widget 
    select wt.template_id, 
           wt.widget 
      into v_template_id, 
           v_widget 
      from widget_templates wt
     where wt.name = p_template_name;

    if NOT FOUND then
      raise EXCEPTION ''-20000: No widget template exists with the name % in dtype_wdgt_tmpl__set_param_value'', p_template_name;
    end if;

    -- Get the param id
    select param_id into v_param_id from dtype_widget_params
     where widget = v_widget 
       and param = p_param;

    if NOT FOUND then
      raise EXCEPTION ''-20000: No parameter named % exists for the widget % in dtype_widget__set_param_value'', p_param, v_widget;
    end if;  

    -- Check if an old value exists
    -- Determine if a previous value exists
    select count(1) into v_prev_value from dual 
      where exists (select 1 from dtype_widget_template_params
                    where template_id = v_template_id
                    and param_id = v_param_id);
    
    if v_prev_value > 0 then
      -- Update the value
      update dtype_widget_template_params set
        param_type = p_param_type,
        param_source = p_param_source,
        value = p_value
      where
        template_id = v_template_id
      and
        param_id = v_param_id;
    else
      -- Insert a new value
      insert into dtype_widget_template_params
        (template_id, param_id, param_type, param_source, value)
      values
        (v_template_id, v_param_id, p_param_type, p_param_source, p_value);
    end if;

    return 0; 
end;' language 'plpgsql';
