WITH FUNCTION shortest_path_function(
  start_nodes_translation      /*>12c|SYS_REFCURSOR|>12c*//*12c|/*spStartVCVARCHAR2spStartVC*//*spStartCLCLOBspStartCL*/|12c*/
, end_nodes_translation        /*spEndVCVARCHAR2spEndVC*//*spEndCLCLOBspEndCL*/
, path_connections_translation /*spPathVCVARCHAR2spPathVC*//*spPathCLCLOBspPathCL*/
, k                            NUMBER
, path_mode                    VARCHAR2
, min_hops                     NUMBER   default 0
, max_hops                     NUMBER   default 9223372036854775807
)
RETURN xmltype IS
  /*spCreateView/*12c|start_nodes_view      VARCHAR2(4000) := 'snv_' || rawtohex(sys_guid());|12c*/
  end_nodes_view        VARCHAR2(4000) := 'env_' || rawtohex(sys_guid());
  path_connections_view VARCHAR2(4000) := 'pcv_' || rawtohex(sys_guid());spCreateView*/
  /*spCreateTableshortest_paths_table  VARCHAR2(4000) := 'spt_' || rawtohex(sys_guid());spCreateTable*/
  start_nodes_cursor    SYS_REFCURSOR/*>12c| := start_nodes_translation|>12c*/;
  start_src_table       VARCHAR2(4000);
  start_src_key         VARCHAR2(4000);
  end_dst_table         VARCHAR2(4000);
  end_dst_key           VARCHAR2(4000);
  end_curs              SYS_REFCURSOR;
  level_start           NUMBER;
  level_end             NUMBER;
  num_hops              NUMBER;
  top_k_reached         BOOLEAN;
  vertex_cnt            NUMBER;
  idx                   NUMBER;
  current_vertex_table  VARCHAR2(4000);
  current_vertex_key    VARCHAR2(4000);
  num_paths             NUMBER;
  neighbors_curs        SYS_REFCURSOR;
  dst_table             VARCHAR2(4000);
  dst_key               VARCHAR2(4000);
  exp                   VARCHAR2(4000);
  edge_table            VARCHAR2(4000);
  edge_key              VARCHAR2(4000);
  previous_idx          NUMBER;
  return_curs           SYS_REFCURSOR;
  exp_path              /*expPathClobCLOBexpPathClob*//*expPathVCVARCHAR2(4000)expPathVC*/;
  paths_found           BOOLEAN;
  is_good_path          BOOLEAN;
  return_xml            XMLTYPE;

  TYPE num_list   IS TABLE OF NUMBER INDEX BY VARCHAR2(4000);
  num_paths_list        NUM_LIST;
  TYPE num_array  IS VARRAY(2147483647) OF NUMBER;
  TYPE str_array  IS VARRAY(2147483647) OF VARCHAR2(4000);
  dst_table_queue       STR_ARRAY := STR_ARRAY();
  dst_key_queue         STR_ARRAY := STR_ARRAY();
  exp_queue             STR_ARRAY := STR_ARRAY();
  edge_table_queue      STR_ARRAY := STR_ARRAY();
  edge_key_queue        STR_ARRAY := STR_ARRAY();
  prev_idx_queue        NUM_ARRAY := NUM_ARRAY();
  num_hops_queue        NUM_ARRAY := NUM_ARRAY();

  /*spCreateXmlreturn_domdoc         dbms_xmldom.DOMDocument;
  root_node             dbms_xmldom.DOMNode;
  rowset_element        dbms_xmldom.DOMElement;
  rowset_node           dbms_xmldom.DOMNode;
  row_element           dbms_xmldom.DOMElement;
  row_node              dbms_xmldom.DOMNode;
  col_element           dbms_xmldom.DOMElement;
  col_node              dbms_xmldom.DOMNode;
  col_name_text         dbms_xmldom.DOMText;
  col_name_node         dbms_xmldom.DOMNode;spCreateXml*/

  pragma autonomous_transaction;
BEGIN
  /*spCreateView-- Create views
  /*12c|execute immediate|12c*/
  /*12c|  'CREATE VIEW ' || start_nodes_view || ' AS ' || start_nodes_translation;|12c*/

  execute immediate
    'CREATE VIEW ' || end_nodes_view || ' AS ' || end_nodes_translation;

  execute immediate
    'CREATE VIEW ' || path_connections_view || ' AS ' || path_connections_translation;spCreateView*/

  /*spCreateTable-- Create table with shortest paths
  execute immediate
    'CREATE TABLE ' || shortest_paths_table || '(
       src_table    varchar2(4000)
     , src_key      varchar2(4000)
     , dst_table    varchar2(4000)
     , dst_key      varchar2(4000)
     , exp_path     /*expPathClobCLOBexpPathClob*//*expPathVCvarchar2(4000)expPathVC*/
     )';spCreateTable*/

  /*spCreateXmlreturn_domdoc := dbms_xmldom.newDomDocument;
  root_node := dbms_xmldom.makeNode(return_domdoc);
  rowset_element := dbms_xmldom.createElement(return_domdoc, 'ROWSET' );
  rowset_node := dbms_xmldom.appendChild(root_node,dbms_xmldom.makeNode(rowset_element));spCreateXml*/

  paths_found := false;

  -- Find paths for every start node
  /*12c|open start_nodes_cursor for |12c*/
  /*12c|  'SELECT "src_table", "src_key" FROM ' |||12c*/
  /*12c|/*spCreateView  start_nodes_view;spCreateView*/|12c*/
  /*12c|/*spNoViews  '(' || start_nodes_translation || ')';spNoViews*/|12c*/

  fetch start_nodes_cursor into start_src_table, start_src_key;

  while start_nodes_cursor%FOUND loop
    -- Initialize data structures
    vertex_cnt := 1;
    num_paths_list.delete;
    dst_table_queue.delete;
    dst_key_queue.delete;
    exp_queue.delete;
    edge_table_queue.delete;
    edge_key_queue.delete;
    prev_idx_queue.delete;
    num_hops_queue.delete;

    -- Insert 0-length path to start node
    dst_table_queue.extend();
    dst_table_queue(vertex_cnt) := start_src_table;
    dst_key_queue.extend();
    dst_key_queue(vertex_cnt) := start_src_key;
    exp_queue.extend();
    exp_queue(vertex_cnt) := '';
    edge_table_queue.extend();
    edge_table_queue(vertex_cnt) := '';
    edge_key_queue.extend();
    edge_key_queue(vertex_cnt) := '';
    prev_idx_queue.extend();
    prev_idx_queue(vertex_cnt) := -1;
    num_hops_queue.extend();
    num_hops_queue(vertex_cnt) := 0;
    vertex_cnt := vertex_cnt + 1;
    if min_hops = 0 then
      num_paths_list(start_src_table || '|' || start_src_key) := 1;
    end if;

    -- Expand reachability graph from start node until we reach
    -- all end nodes k times or until we cannot expand anymore
    level_start := 1;
    level_end := 1;

    open end_curs for
      'SELECT "dst_table", "dst_key" FROM ' ||
        /*spCreateViewend_nodes_view;spCreateView*/
        /*spNoViews'(' || end_nodes_translation || ')';spNoViews*/
    fetch end_curs into end_dst_table, end_dst_key;
    while end_curs%FOUND AND level_start <= level_end loop

      -- Find if we already have k paths to end node
      top_k_reached := false;
      if level_start <= level_end then
        BEGIN
          top_k_reached := num_paths_list(end_dst_table || '|' || end_dst_key) = k;
         EXCEPTION when NO_DATA_FOUND then null;
        END;
      end if;

      -- Find paths to end node
      while (level_start <= level_end AND NOT top_k_reached) loop

        -- Loop through each vertex in current level
        idx := level_start;
        while (idx <= level_end AND NOT top_k_reached) loop

          current_vertex_table := dst_table_queue(idx);
          current_vertex_key := dst_key_queue(idx);
          num_hops := num_hops_queue(idx);

          if num_hops < max_hops then
            -- Get neighbors of current vertex
            open neighbors_curs for
              'SELECT pc."dst_table", pc."dst_key", pc."exp", pc."edge_table", pc."edge_key" FROM ' ||
              /*spCreateViewpath_connections_viewspCreateView*/
              /*spNoViews'(' || path_connections_translation || ')'spNoViews*/
               || ' pc
               WHERE pc."src_table" = :current_vertex_table
                 AND pc."src_key" = :current_vertex_key'
            using current_vertex_table, current_vertex_key;

            -- Loop through each neighbor
            fetch neighbors_curs into dst_table, dst_key, exp, edge_table, edge_key;
            while neighbors_curs%FOUND loop

              -- Find if there are k paths to the neighbor
              BEGIN
                num_paths := num_paths_list(dst_table || '|' || dst_key);
               EXCEPTION WHEN NO_DATA_FOUND THEN num_paths := 0;
              END;

              if num_paths < k then
      	        -- Check path mode conditions
                is_good_path := true;
                case path_mode
                  when 'WALK' then
                    null;
                  when 'ACYCLIC' then
                    previous_idx := idx;
                    while previous_idx <> -1 AND is_good_path loop
                      if (dst_table = dst_table_queue(previous_idx) AND dst_key = dst_key_queue(previous_idx)) then
                        -- Found cycle, do not insert neighbor
                        is_good_path := false;
                      end if;
                      previous_idx := prev_idx_queue(previous_idx);
                    end loop;
                  when 'SIMPLE' then
                    if (idx > 1 AND current_vertex_table = start_src_table AND current_vertex_key = start_src_key) then
		      -- This is expansion of a simple cycle, do not insert neighbor
                      is_good_path := false;
		    else
                      previous_idx := idx;
        	      while previous_idx <> -1 AND is_good_path loop
		        if (dst_table = dst_table_queue(previous_idx) AND dst_key = dst_key_queue(previous_idx)) then
		          -- Found cycle, check if it is simple
		          if NOT (dst_table = start_src_table AND dst_key = start_src_key) then
		            -- Found non-simple cycle, do not insert neighbor
                            is_good_path := false;
		          end if;
                        end if;
                        previous_idx := prev_idx_queue(previous_idx);
                      end loop;
                    end if;
                  when 'TRAIL' then
                    previous_idx := idx;
                    while previous_idx <> -1 AND is_good_path loop
                      if (edge_table = edge_table_queue(previous_idx) AND edge_key = edge_key_queue(previous_idx)) then
                        -- Found cycle, do not insert neighbor
                        is_good_path := false;
                      end if;
                      previous_idx := prev_idx_queue(previous_idx);
                    end loop;
                else
                  raise_application_error(-20000, 'Path mode not supported: ' || path_mode);
                end case;

                if is_good_path then
                  -- Add neighbor to the queue
                  dst_table_queue.extend();
                  dst_table_queue(vertex_cnt) := dst_table;
                  dst_key_queue.extend();
                  dst_key_queue(vertex_cnt) := dst_key;
                  exp_queue.extend();
                  exp_queue(vertex_cnt) := exp;
                  edge_table_queue.extend();
                  edge_table_queue(vertex_cnt) := edge_table;
                  edge_key_queue.extend();
                  edge_key_queue(vertex_cnt) := edge_key;
                  prev_idx_queue.extend();
                  prev_idx_queue(vertex_cnt) := idx;
                  num_hops_queue.extend();
                  num_hops_queue(vertex_cnt) := num_hops + 1;
                  vertex_cnt := vertex_cnt + 1;

                  -- Update number of paths found to the neighbor
                  if min_hops <= num_hops_queue(vertex_cnt - 1) then
                    num_paths := num_paths + 1;
                    num_paths_list(dst_table || '|' || dst_key) := num_paths;
                  end if;
                end if;
              end if;

              -- Find if neighbor is current destination and we have reached k
              if (dst_table = end_dst_table AND dst_key = end_dst_key AND num_paths = k) then
                top_k_reached := true;
              end if;

              fetch neighbors_curs into dst_table, dst_key, exp, edge_table, edge_key;
            end loop;
            close neighbors_curs;

          end if;
          idx:= idx + 1;
        end loop;

        -- If we found k paths for current destination, we keep expanding
        -- same level for the next destination node
        if top_k_reached then
          level_start := idx;
        else
          level_start := level_end + 1;
        end if;

        -- If we are already at the next level, update end index of the level
        if level_start = level_end + 1 then
          level_end := vertex_cnt - 1;
        end if;

      end loop;
      fetch end_curs into end_dst_table, end_dst_key;
    end loop;
    close end_curs;

    -- Loop through the queue to build and store found shortest paths
    for i in 1..(vertex_cnt - 1) loop
      end_dst_table := dst_table_queue(i);
      end_dst_key := dst_key_queue(i);
      exp := exp_queue(i);
      previous_idx := prev_idx_queue(i);
      num_hops := num_hops_queue(i);

      if num_hops >= min_hops then
        BEGIN
          -- Find if element at the queue is a destination node
          execute immediate
            'select "dst_table", "dst_key" from ' ||
              /*spCreateViewend_nodes_viewspCreateView*/
              /*spNoViews'(' || end_nodes_translation || ')'spNoViews*/
             || '
             where "dst_table" = :end_dst_table
               and "dst_key" = :end_dst_key'
          using end_dst_table, end_dst_key;

         -- Build path
          exp_path := exp || '</EXP_PATH>';
          while previous_idx <> -1 loop
            exp := exp_queue(previous_idx);
            exp_path := exp || exp_path;
            previous_idx := prev_idx_queue(previous_idx);
          end loop;
          exp_path := '<EXP_PATH>' || exp_path;

          -- Insert path
          /*spCreateTableexecute immediate
            'INSERT INTO ' || shortest_paths_table || ' (src_table, src_key, dst_table, dst_key, exp_path)
             VALUES (:src_table, :src_key, :dst_table, :dst_key, :exp_path)'
          using start_src_table, start_src_key, end_dst_table, end_dst_key, exp_path;spCreateTable*/

          /*spCreateXmlrow_element := dbms_xmldom.createElement(return_domdoc, 'ROW' );
          row_node := dbms_xmldom.appendChild(rowset_node,dbms_xmldom.makeNode(row_element));

          col_element := dbms_xmldom.createElement(return_domdoc, 'SRC_TABLE' );
          col_node := dbms_xmldom.appendChild(row_node,dbms_xmldom.makeNode(col_element));
          col_name_text := dbms_xmldom.createTextNode(return_domdoc, start_src_table );
          col_name_node := dbms_xmldom.appendChild(col_node,dbms_xmldom.makeNode(col_name_text));

          col_element := dbms_xmldom.createElement(return_domdoc, 'SRC_KEY' );
          col_node := dbms_xmldom.appendChild(row_node,dbms_xmldom.makeNode(col_element));
          col_name_text := dbms_xmldom.createTextNode(return_domdoc, start_src_key );
          col_name_node := dbms_xmldom.appendChild(col_node,dbms_xmldom.makeNode(col_name_text));

          col_element := dbms_xmldom.createElement(return_domdoc, 'DST_TABLE' );
          col_node := dbms_xmldom.appendChild(row_node,dbms_xmldom.makeNode(col_element));
          col_name_text := dbms_xmldom.createTextNode(return_domdoc, end_dst_table );
          col_name_node := dbms_xmldom.appendChild(col_node,dbms_xmldom.makeNode(col_name_text));

          col_element := dbms_xmldom.createElement(return_domdoc, 'DST_KEY' );
          col_node := dbms_xmldom.appendChild(row_node,dbms_xmldom.makeNode(col_element));
          col_name_text := dbms_xmldom.createTextNode(return_domdoc, end_dst_key );
          col_name_node := dbms_xmldom.appendChild(col_node,dbms_xmldom.makeNode(col_name_text));

          col_element := dbms_xmldom.createElement(return_domdoc, 'EXP_PATH' );
          col_node := dbms_xmldom.appendChild(row_node,dbms_xmldom.makeNode(col_element));
          col_name_text := dbms_xmldom.createTextNode(return_domdoc, exp_path );
          col_name_node := dbms_xmldom.appendChild(col_node,dbms_xmldom.makeNode(col_name_text));spCreateXml*/

          paths_found := true;
         EXCEPTION when NO_DATA_FOUND then null;
        END;
      end if;

    end loop;

    fetch start_nodes_cursor into start_src_table, start_src_key;
  end loop;
  close start_nodes_cursor;

  /*spCreateView-- Drop views
  /*12c|BEGIN|12c*/
  /*12c|  execute immediate 'drop view ' || start_nodes_view;|12c*/
  /*12c| EXCEPTION when others then null;|12c*/
  /*12c|END;|12c*/

  BEGIN
    execute immediate 'drop view ' || end_nodes_view;
   EXCEPTION when others then null;
  END;

  BEGIN
    execute immediate 'drop view ' || path_connections_view;
   EXCEPTION when others then null;
  END;spCreateView*/
  -- Create return xmltype
  if paths_found then
    /*spCreateTableOPEN return_curs for
      'SELECT src_table, src_key,
              dst_table, dst_key,
              exp_path
       FROM ' || shortest_paths_table;
    return_xml := xmltype(return_curs);
    CLOSE return_curs;spCreateTable*/

    /*spCreateXmlreturn_xml := dbms_xmldom.getXmlType(return_domdoc);
    dbms_xmldom.freeDocument(return_domdoc);spCreateXml*/

  else
    return_xml := xmltype('<?xml version="1.0"?><ROWSET></ROWSET>');
  end if;

  /*spCreateTable-- Drop table with shortest paths
  BEGIN
    execute immediate 'drop table ' || shortest_paths_table;
   EXCEPTION when others then null;
  END;spCreateTable*/

  return return_xml;

EXCEPTION when others then
  /*spCreateView-- Drop views
  /*12c|BEGIN|12c*/
  /*12c|  execute immediate 'drop view ' || start_nodes_view;|12c*/
  /*12c| EXCEPTION when others then null;|12c*/
  /*12c|END;|12c*/

  BEGIN
    execute immediate 'drop view ' || end_nodes_view;
   EXCEPTION when others then null;
  END;

  BEGIN
    execute immediate 'drop view ' || path_connections_view;
   EXCEPTION when others then null;
  END;spCreateView*/

  /*spCreateTable-- Drop table with shortest paths
  BEGIN
    execute immediate 'drop table ' || shortest_paths_table;
   EXCEPTION when others then null;
  END;spCreateTable*/

  -- Raise exception
  raise;
END shortest_path_function;
