CLASS zcl_idoc_edidd DEFINITION
  PUBLIC
  CREATE PROTECTED .

  PUBLIC SECTION.
    TYPES tt_edi_api TYPE STANDARD TABLE OF edi_iapi06.

    CLASS-METHODS create_with_data
      IMPORTING
        !iv_idoc_type      TYPE edi_idoctp
        !iv_idoc_extension TYPE edi_cimtyp
        !it_edidd          TYPE edidd_tt OPTIONAL
      RETURNING
        VALUE(ro_instance) TYPE REF TO zcl_idoc_edidd
      RAISING
        zcx_idoc_exceptions .
    METHODS add_segment
      IMPORTING
        !is_edidd         TYPE edidd
      RETURNING
        VALUE(ro_segment) TYPE REF TO zcl_idoc_edidd_segment
      RAISING
        zcx_idoc_exceptions .
    METHODS get_edidd
      RETURNING
        VALUE(rt_edidd) TYPE edidd_tt .
    METHODS get_segments
      IMPORTING
        !iv_name           TYPE edilsegtyp OPTIONAL
      RETURNING
        VALUE(ro_segments) TYPE REF TO cl_object_collection .
    METHODS remove_segment
      IMPORTING
        !io_segment       TYPE REF TO zcl_idoc_edidd_segment
      RETURNING
        VALUE(rv_removed) TYPE flag .
  PROTECTED SECTION.

    TYPES:
      tt_idoc_structure_sorted TYPE SORTED TABLE OF edi_iapi06
        WITH UNIQUE KEY idoctyp cimtyp nr
        WITH UNIQUE SORTED KEY segtyp COMPONENTS segtyp.

    DATA ao_segments TYPE REF TO cl_object_collection.
    DATA at_idoc_structure TYPE tt_edi_api.
    DATA at_idoc_structure_sorted TYPE tt_idoc_structure_sorted.
    DATA av_idoc_extension TYPE edi_cimtyp.
    DATA av_idoc_type TYPE edi_idoctp .

    METHODS add_segment_do
          FINAL
      IMPORTING
        !is_edidd         TYPE edidd
      RETURNING
        VALUE(ro_segment) TYPE REF TO zcl_idoc_edidd_segment
      RAISING
        zcx_idoc_exceptions .
    METHODS constructor
      IMPORTING
        !iv_idoc_type      TYPE edi_idoctp
        !iv_idoc_extension TYPE edi_cimtyp
        !it_edidd          TYPE edidd_tt OPTIONAL
        !it_idoc_structure TYPE tt_edi_api
      RAISING
        zcx_idoc_exceptions .
  PRIVATE SECTION.

    METHODS add_segments_in_given_sequence
      IMPORTING
        !it_edidd TYPE edidd_tt
      RAISING
        zcx_idoc_exceptions .
ENDCLASS.



CLASS zcl_idoc_edidd IMPLEMENTATION.


  METHOD add_segment.
    DATA:
      lo_parent TYPE REF TO zcl_idoc_edidd_segment.

    READ TABLE at_idoc_structure_sorted REFERENCE INTO DATA(ld_idoc_structure)
      WITH KEY segtyp COMPONENTS segtyp = is_edidd-segnam.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_idoc_exceptions
        EXPORTING
          textid = zcx_idoc_exceptions=>segment_incorrect_for_idoc
          msgv1  = is_edidd-segnam
          msgv2  = av_idoc_type
          msgv3  = av_idoc_extension.
    ENDIF.

    IF ld_idoc_structure->parseg IS INITIAL.
      ro_segment = add_segment_do( is_edidd ).
    ELSE.
      DATA(lo_segments) = get_segments( ld_idoc_structure->parseg ).
      IF lo_segments->is_empty( ).
        RAISE EXCEPTION TYPE zcx_idoc_exceptions
          EXPORTING
            textid = zcx_idoc_exceptions=>parent_segment_not_found
            msgv1  = ld_idoc_structure->parseg.
      ENDIF.

      " add new segment to the last parent segment
      DATA(lo_iterator) = lo_segments->get_iterator( ).
      WHILE lo_iterator->has_next( ).
        lo_parent ?= lo_iterator->get_next( ).
      ENDWHILE.

      ro_segment = lo_parent->add_segment( is_edidd ).
    ENDIF.
  ENDMETHOD.


  METHOD add_segments_in_given_sequence.
    TYPES:
      BEGIN OF yls_last_segment,
        number  TYPE posno,
        segment TYPE REF TO zcl_idoc_edidd_segment,
      END OF yls_last_segment,
      ylt_last_segments TYPE SORTED TABLE OF yls_last_segment
        WITH UNIQUE KEY number.

    DATA:
      lt_last_segments TYPE ylt_last_segments,
      lo_segment       TYPE REF TO zcl_idoc_edidd_segment.

    LOOP AT it_edidd REFERENCE INTO DATA(ld_edidd).
      READ TABLE at_idoc_structure_sorted REFERENCE INTO DATA(ld_idoc_structure)
        WITH KEY segtyp COMPONENTS segtyp = ld_edidd->segnam.
      IF sy-subrc <> 0.
        RAISE EXCEPTION TYPE zcx_idoc_exceptions
          EXPORTING
            textid = zcx_idoc_exceptions=>segment_incorrect_for_idoc
            msgv1  = ld_edidd->segnam
            msgv2  = av_idoc_type
            msgv3  = av_idoc_extension.
      ENDIF.

      DATA(lo_parent) = me.
      IF ld_idoc_structure->parpno IS NOT INITIAL.
        READ TABLE lt_last_segments REFERENCE INTO DATA(ld_last_segment)
          WITH TABLE KEY number = ld_idoc_structure->parpno.
        IF sy-subrc <> 0.
          RAISE EXCEPTION TYPE zcx_idoc_exceptions
            EXPORTING
              textid = zcx_idoc_exceptions=>parent_segment_not_found
              msgv1  = ld_idoc_structure->parseg.
        ENDIF.

        lo_parent = ld_last_segment->segment.
      ENDIF.

      CREATE OBJECT lo_segment
        EXPORTING
          iv_idoc_type      = av_idoc_type
          iv_idoc_extension = av_idoc_extension
          is_edidd          = ld_edidd->*
          it_idoc_structure = at_idoc_structure.
      lo_parent->ao_segments->add( lo_segment ).

      READ TABLE lt_last_segments REFERENCE INTO ld_last_segment
        WITH TABLE KEY number = ld_idoc_structure->nr.
      IF sy-subrc <> 0.
        CREATE DATA ld_last_segment.
        ld_last_segment->number = ld_idoc_structure->nr.
        INSERT ld_last_segment->* INTO TABLE lt_last_segments REFERENCE INTO ld_last_segment.
      ENDIF.

      ld_last_segment->segment = lo_segment.
    ENDLOOP.
  ENDMETHOD.


  METHOD add_segment_do.
    DATA:
      lv_added    TYPE flag VALUE abap_false,
      lt_segments TYPE TABLE OF REF TO zcl_idoc_edidd_segment,
      lo_segment  TYPE REF TO zcl_idoc_edidd_segment.

    CREATE OBJECT ro_segment
      EXPORTING
        iv_idoc_type      = av_idoc_type
        iv_idoc_extension = av_idoc_extension
        is_edidd          = is_edidd
        it_idoc_structure = at_idoc_structure.

    DATA(lo_iterator) = ao_segments->get_iterator( ).
    WHILE lo_iterator->has_next( ).
      lo_segment ?= lo_iterator->get_next( ).
      APPEND lo_segment TO lt_segments.
    ENDWHILE.

    ao_segments->clear( ).

    READ TABLE at_idoc_structure_sorted REFERENCE INTO DATA(ld_idoc_structure)
      WITH KEY segtyp COMPONENTS segtyp = is_edidd-segnam.
    DATA(lv_new_segment_position) = ld_idoc_structure->nr.

    LOOP AT lt_segments INTO lo_segment.
      " new segments still needs to be added
      IF lv_added = abap_false.
        READ TABLE at_idoc_structure_sorted REFERENCE INTO ld_idoc_structure
          WITH KEY segtyp COMPONENTS segtyp = lo_segment->get_name( ).
        DATA(lv_old_segment_position) = ld_idoc_structure->nr.

        IF lv_old_segment_position > lv_new_segment_position.
          ao_segments->add( ro_segment ).
          lv_added = abap_true.
        ENDIF.
      ENDIF.

      ao_segments->add( lo_segment ).
    ENDLOOP.

    " if segment was not inserted between other segments
    " then add it to the end of the collection
    CHECK lv_added = abap_false.
    ao_segments->add( ro_segment ).
  ENDMETHOD.


  METHOD constructor.
    av_idoc_type = iv_idoc_type.
    av_idoc_extension = iv_idoc_extension.
    at_idoc_structure[] = it_idoc_structure[].
    at_idoc_structure_sorted[] = it_idoc_structure[].

    CREATE OBJECT ao_segments.

    CHECK it_edidd[] IS NOT INITIAL.
    add_segments_in_given_sequence( it_edidd ).
  ENDMETHOD.


  METHOD create_with_data.
    DATA:
      lt_idoc_structure TYPE tt_edi_api.

    CALL FUNCTION 'EDI_IDOC_SYNTAX_GET'
      EXPORTING
        pi_idoctyp       = iv_idoc_type
        pi_cimtyp        = iv_idoc_extension
      TABLES
        pt_syntax_table  = lt_idoc_structure
      EXCEPTIONS
        syntax_not_found = 1
        OTHERS           = 2.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_idoc_exceptions.
    ENDIF.

    CREATE OBJECT ro_instance
      EXPORTING
        iv_idoc_type      = iv_idoc_type
        iv_idoc_extension = iv_idoc_extension
        it_edidd          = it_edidd
        it_idoc_structure = lt_idoc_structure.
  ENDMETHOD.


  METHOD get_edidd.
    DATA:
      lo_segment TYPE REF TO zcl_idoc_edidd_segment.

    DATA(lo_iterator) = ao_segments->get_iterator( ).
    WHILE lo_iterator->has_next( ).
      lo_segment ?= lo_iterator->get_next( ).
      DATA(lt_edidd) = lo_segment->get_edidd( ).
      APPEND LINES OF lt_edidd TO rt_edidd.
    ENDWHILE.
  ENDMETHOD.


  METHOD get_segments.
    DATA:
      lv_found         TYPE flag VALUE abap_false,
      lo_segment       TYPE REF TO zcl_idoc_edidd_segment,
      lo_child_segment TYPE REF TO zcl_idoc_edidd_segment.

    CREATE OBJECT ro_segments.

    DATA(lo_iterator) = ao_segments->get_iterator( ).
    WHILE lo_iterator->has_next( ).
      lo_segment ?= lo_iterator->get_next( ).

      IF iv_name IS SUPPLIED.
        IF lo_segment->get_name( ) = iv_name.
          lv_found = abap_true.
          ro_segments->add( lo_segment ).
        ENDIF.
      ELSE.
        ro_segments->add( lo_segment ).
      ENDIF.

      " if segments with given name was found on this level
      " then do not search deeper
      CHECK lv_found = abap_false.

      DATA(lo_child_segments) = lo_segment->get_segments( iv_name = iv_name ).
      DATA(lo_child_iterator) = lo_child_segments->get_iterator( ).
      WHILE lo_child_iterator->has_next( ).
        lo_child_segment ?= lo_child_iterator->get_next( ).
        ro_segments->add( lo_child_segment ).
      ENDWHILE.
    ENDWHILE.
  ENDMETHOD.


  METHOD remove_segment.
    DATA:
      lo_segment TYPE REF TO zcl_idoc_edidd_segment.

    rv_removed = abap_false.

    DATA(lo_iterator) = ao_segments->get_iterator( ).
    WHILE lo_iterator->has_next( ).
      lo_segment ?= lo_iterator->get_next( ).
      IF lo_segment = io_segment.
        ao_segments->remove( io_segment ).
        rv_removed = abap_true.
        RETURN.
      ENDIF.

      IF lo_segment->remove_segment( io_segment ).
        rv_removed = abap_true.
        RETURN.
      ENDIF.
    ENDWHILE.
  ENDMETHOD.
ENDCLASS.
