#---------------------------------------------------------------------------*
#
#  @file goil_code_generation.galgas
#
#  @section desc File description
#
#  Code generation functions
#
#  @section copyright Copyright
#
#  Goil OIL compiler, part of Trampoline RTOS
#
#  Trampoline is copyright (c) CNRS, University of Nantes,
#  Ecole Centrale de Nantes
#  Trampoline is protected by the French intellectual property law.
#
#  This software is distributed under the GNU Public Licence V2.
#  Check the LICENSE file in the root directory of Trampoline
#
#  $Date$
#  $Rev$
#  $Author$
#  $URL$
#
#---------------------------------------------------------------------------*

#--------------------------------------------------------------------
# Custom type generation
# This function generates the appropriate type according
# to the number of objects
#--------------------------------------------------------------------
proc generate_signed_type
  ?@uint64 count
  ?@string err
  !@string type {
  # compute the max for each size
  let @uint64 max_s8  = 1 << 7
  let @uint64 max_s16 = 1 << 15
  let @uint64 max_s32 = 1 << 31
  
  if count < max_s8 then
    type = "s8"
  else
    if count < max_s16 then
      type = "s16"
    else
      if count < max_s32 then
        type = "s32"
      else
        type = ""
        error @location.here : err
      end
    end
  end
}

proc generate_unsigned_type
  ?@uint64 count
  ?@string err
  !@string type {
  # compute the max for each size
  let @uint64 max_u8  = 1 << 8
  let @uint64 max_u16 = 1 << 16
  let @uint64 max_u32 = 1 << 32

  if count < max_u8 then
    type = "u8"
  else
    if count < max_u16 then
      type = "u16"
    else
      if count < max_u32 then
        type = "u32"
      else
        type = ""
        error @location.here : err
      end
    end
  end
}

proc generate_mask_type
  ?@uint64 count
  ?@string err
  !@string type {
  # compute the max for each size
  let @uint64 max_u8  = 8
  let @uint64 max_u16 = 16
  let @uint64 max_u32 = 32

  if count <= max_u8 then
    type = "u8"
  else
    if count <= max_u16 then
      type = "u16"
    else
      if count <= max_u32 then
        type = "u32"
      else
        type = ""
        error @location.here : err
      end
    end
  end
}

#routine generate_types
#  ?@task_map          tasks
#  ?@isr_map           isrs
#  ?@alarm_map         alarms
#  ?@counter_map       counters
#  ?@resource_map      resources
#  ?@scheduletable_map schedtables
#  ?@app_map           apps
#  ?@uint              max_priority
#  ?!@string           types
#:
#  @string type;
#  @uint64 max := 0L;
#  @uint64 c;
#
#  # compute the type for tpl_proc_id
#  c := [[tasks count] uint64]+[[isrs count] uint64];
#  if c > max then max := c; end if;
#  generate_signed_type !c !"Too many processes (tasks and ISRs)" ?type;
#  types := [types stringByReplacingStringByString !"$PROC_ID_T$" !type];
#
#  # compute the type for tpl_priority
#  generate_signed_type ![max_priority uint64] !"Maximum priority too high" ?type;
#  #message "tpl_priority:         ".type."\n";
#  types := [types stringByReplacingStringByString !"$PRIORITY_T$" !type];
#
#  # compute the type for the activation count
#  # iterate through the tasks to get the max
#  @uint max_act := 0;
#  foreach tasks (@lstring lkey @task_obj task) do
#    cast [task activation] :
#    when == @uint32_class ui do if [ui value] > max_act then max_act := [ui value]; end if;
#    else error lkey : "Internal error, task ".[lkey string]." has wrong type for the ACTIVATION attribute";
#    end cast;
#  end foreach;
#  generate_unsigned_type ![max_act uint64] !"Maximum ACTIVATION too high" ?type;
#  #message "tpl_activate_counter: ".type."\n";
#  types := [types stringByReplacingStringByString !"$ACTIVATION_T$" !type];
#
#  # compute the type for the event mask
#  # iterate through the tasks to get the maximum number of events
#  @uint max_ev := 0;
#  foreach tasks (@lstring lkey @task_obj task) do
#    @lstringlist evts := [task events];
#    if [evts length] > max_ev then max_ev := [evts length]; end if;
#  end foreach;
#  generate_mask_type ![max_ev uint64] !"Too much events" ?type;
#  types := [types stringByReplacingStringByString !"$EVENTMASK_T$" !type];
#
#  # compute the type for tpl_alarm_id
#  c := [[alarms count] uint64];
#  if c > max then max := c; end if;
#  generate_unsigned_type !c !"too many alarms" ?type;
#  #message "tpl_alarm_id:         ".type."\n";
#  types := [types stringByReplacingStringByString !"$ALARM_ID_T$" !type];
#
#  # compute the type for tpl_resource_id
#  c := [[resources count] uint64];
#  if c > max then max := c; end if;
#  generate_unsigned_type !c !"too many resources" ?type;
#  #message "tpl_resource_id:      ".type."\n";
#  types := [types stringByReplacingStringByString !"$RESOURCE_ID_T$" !type];
#
#  # compute the type for tpl_counter_id
#  c := [[counters count] uint64];
#  if c > max then max := c; end if;
#  generate_unsigned_type !c !"too many counters" ?type;
#  #message "tpl_counter_id:       ".type."\n";
#  types := [types stringByReplacingStringByString !"$COUNTER_ID_T$" !type];
#
#  # compute the type for tpl_schedtable_id
#  c := [[schedtables count] uint64];
#  if c > max then max := c; end if;
#  generate_unsigned_type !c !"too many schedule tables" ?type;
#  #message "tpl_schedtable_id:           ".type."\n";
#  types := [types stringByReplacingStringByString !"$SCHEDTABLE_ID_T$" !type];
#
#  # compute the type for tpl_timeobj_id as the max of the number of
#  # alarms and schedule tables
#  if [alarms count] > [schedtables count] then
#    c := [[alarms count] uint64];
#  else
#    c := [[schedtables count] uint64];
#  end if;
#  if c > max then max := c; end if;
#  generate_unsigned_type !c !"too many time objects" ?type;
#  types := [types stringByReplacingStringByString !"$TIMEOBJ_ID_T$" !type];
#
#  # compute the type for tpl_app_id
#  generate_unsigned_type ![[apps count] uint64]+1L !"too many OS applications" ?type;
#  #message "tpl_app_id:           ".type."\n";
#  doReplace !?types !"$OSAPPLICATION_ID_T$" !type;
#
#  # compute the type for tpl_tf_id (trusted function index)
#  @stringset tfs [emptySet];
#
#  foreach apps do
#    cast [app trusted] :
#    when == @app_trusted_true t do
#      foreach [t trusted_fcts] (@string s) do
#        tfs += !s;
#      end foreach;
#    else end cast;
#  end foreach;
#
#  generate_unsigned_type ![[tfs count] uint64] !"Too many Trusted Functions" ?type;
#  doReplace !?types !"$TRUSTED_FCT_ID_T$" !type;
#
#  # compute the type for tpl_generic_id
#  generate_unsigned_type !max !"too many objects" ?type;
#  #message "tpl_generic_id:           ".type."\n";
#  doReplace !?types !"$GENERIC_ID_T$" !type;
#
#end routine;

#--------------------------------------------------------------------
#routine generate_timing_prot
#    ?@task_map     tasks
#    ?@resource_map rez
#    ?!@string obj_header
#:
#    @uint max_sched_wd := [rez count] + 4 * [tasks count];
#
#    obj_header := [obj_header stringByReplacingStringByString !"$MAX_WATCH_DOG$" ![max_sched_wd string]];
#end routine;

#--------------------------------------------------------------------
# Actual Task priority computation
#--------------------------------------------------------------------
#routine compute_actual_task_priority
#    ?@task_map  tasks
#    ?!@prio_map  prio_for_task
#    !@uint      max_prio
#:
#    @objs_by_prio tl [emptySortedList];
#
#    #
#    # fill the sorted list
#    # This list is sorted from the lower priority task
#    # to the higher priority task
#    #
#    foreach tasks (@lstring task_name @task_obj task) do
#        @basic_type prio_as_basic;
#        @basic_type act_as_basic;
#        @uint prio := 0;
#        @uint act := 0;
#        prio_as_basic := [task priority];
#        act_as_basic := [task activation];
#
#        cast prio_as_basic :
#        when == @uint32_class ui do prio := [ui value];
#        else error [prio_as_basic location] : "TASK PRIORITY attribute undefined";
#        end cast;
#
#        cast act_as_basic :
#        when == @uint32_class ui do act := [ui value];
#        else error [act_as_basic location] : "TASK ACTIVATION attribute undefined";
#        end cast;
#
#        tl += !prio !act !task_name;
#    end foreach;
#
#    #
#    # Get the minimum priority
#    #
#    @uint actual_prio := 1;
#    @uint cur_rel_prio := 0;
#    if [tl length] > 0 then
#        [tl smallest ?cur_rel_prio ?* ?*];
#    end if;
#
#    #
#    # Compute the actual priority of the tasks starting from 1
#    #
#    foreach tl (@uint relative_prio @uint act @lstring tn) do
##        message "Prio = ".[relative_prio string].", nom = ".[tn string]."\n";
#        if (relative_prio > cur_rel_prio) then
#            cur_rel_prio := relative_prio;
#            actual_prio ++;
#        end if;
##        message "Actual prio = ".[actual_prio string].", nom = ".[tn string]."\n";
#        [!?prio_for_task put !tn !actual_prio !act];
#    end foreach;
#
#    max_prio := actual_prio;
#
#end routine;

#--------------------------------------------------------------------
# Actual ISR2 priority computation
#--------------------------------------------------------------------
#routine compute_actual_isr_priority
#    ?@isr_map   isrs
#    ?!@prio_map  prio_for_isr
#    ?@uint      floor
#    !@uint      max_prio
#:
#    @objs_by_prio il [emptySortedList];
#
#    if [isrs count] != 0 then
#        #
#        # fill the sorted list
#        # This list is sorted from the lower priority isr
#        # to the higher priority isr
#        #
#        foreach isrs (@lstring isr_name @isr_obj isr) do
#            @basic_type prio_as_basic;
#            @uint prio := 0;
##            @uint act := 0;
#
#            prio_as_basic := [isr priority];
##            [isr get_activation ?act];
#
#            cast prio_as_basic :
#            when == @uint32_class ui do prio := [ui value];
#            else error [prio_as_basic location] : "ISR PRIORITY attribute undefined";
#            end cast;
#
#    #        cast act_as_basic :
#    #        when == @uint32_class ui do act := [ui value];
#    #        else error [act_as_basic location] : "ISR ACTIVATION attribute undefined";
#    #        end cast;
#
#            il += !prio !1 !isr_name;
#        end foreach;
#
#        #
#        # Get the minimum priority
#        #
#        @uint actual_prio := floor + 1;
#        @uint cur_rel_prio := 0;
#        if [il length] > 0 then
#            [il smallest ?cur_rel_prio ?* ?*];
#        end if;
#
#        #
#        # Compute the actual priority of the isrs starting from floor + 1
#        #
#        foreach il (@uint relative_prio @uint a @lstring isn) do
##            message "Prio = ".[relative_prio string].", nom = ".[isn string]."\n";
#            if (relative_prio > cur_rel_prio) then
#                cur_rel_prio := relative_prio;
#                actual_prio ++;
#            end if;
##            message "Actual prio = ".[actual_prio string].", nom = ".[isn string]."\n";
#            [!?prio_for_isr put !isn !actual_prio !a];
#        end foreach;
#
#        max_prio := actual_prio;
#    else
#        max_prio := floor;
#    end if;
#end routine;

#--------------------------------------------------------------------
# Resource priority computation
#--------------------------------------------------------------------
#routine compute_rez_priority
#    ?@resource_map rez
#    ?@task_map     tasks
#    ?@isr_map      isrs
#    ?@prio_map     prio_for_process
#    !@prio_map     prio_for_rez
#    !@stringMap   comments
#    
#:
#    prio_for_rez := [@prio_map emptyMap];
#    comments := [@stringMap emptyMap];
#
#    #
#    # for each resource, start at a 0 priority.
#    # iterate through the tasks and the isrs.
#    # if the current task or isr may own the resource, and if
#    # the priority of the current task/isr is greater than the
#    # current computed priority for the resource, set the current
#    # computed priority to that priority
#    #
#
#    foreach rez (@lstring rez_name ...) do
#        @uint rez_prio := 0;
#        @uint rez_act := 0;
#        @string rez_comment := "";
#        foreach tasks (@lstring tn @task_obj t) do
#            @lstringlist owned_rez := [t resources];
#            @bool owned;
#            is_in_lstringlist !owned_rez !rez_name ?* ?owned;
#            if owned then
#                @uint prio;
#                @uint act;
#                [prio_for_process get !tn ?prio ?act];
#                if prio > rez_prio then
#                    rez_prio := prio;
#                end if;
#                rez_act := rez_act + act;
#                rez_comment .= " used by task ".[tn string]." (".[prio string].")\n";
#            end if;
#        end foreach;
#        foreach isrs (@lstring isn @isr_obj i) do
#            @lstringlist owned_rez := [i resources];
#            @bool owned;
#            is_in_lstringlist !owned_rez !rez_name ?* ?owned;
#            if owned then
#                @uint prio;
#                @uint act;
#                [prio_for_process get !isn ?prio ?act];
#                if prio > rez_prio then
#                    rez_prio := prio;
#                end if;
#                rez_act := rez_act + act;
#                rez_comment .= " used by isr ".[isn string]." (".[prio string].")\n";
#            end if;
#        end foreach;
#
#        if rez_comment == "" then
#            rez_comment := " none\n";
#        end if;
#
#        #
#        # The higher priority task/isr that may own a resource had its activation
#        # count added but it should not be. So substract it
#        #
#        foreach tasks (@lstring tn @task_obj t) do
#            @lstringlist owned_rez := [t resources];
#            @bool owned;
#            is_in_lstringlist !owned_rez !rez_name ?* ?owned;
#            if owned then
#                @uint prio;
#                @uint act;
#                [prio_for_process get !tn ?prio ?act];
#                if prio == rez_prio then
#                    rez_act := rez_act - act;
#                end if;
#            end if;
#        end foreach;
#        foreach isrs (@lstring isn @isr_obj i) do
#            @lstringlist owned_rez := [i resources];
#            @bool owned;
#            is_in_lstringlist !owned_rez !rez_name ?* ?owned;
#            if owned then
#                @uint prio;
#                @uint act;
#                [prio_for_process get !isn ?prio ?act];
#                if prio == rez_prio then
#                    rez_act := rez_act - act;
#                end if;
#            end if;
#        end foreach;
#
#        [!?prio_for_rez put !rez_name !rez_prio !rez_act];
#        [!?comments put !rez_name !rez_comment];
##        message "Rez = ".[rez_name string]." prio = ".[rez_prio string]."\n";
##        message rez_comment;
#    end foreach;
#end routine;

#--------------------------------------------------------------------
# Compute the number of entry in the fifo for each priority level
#--------------------------------------------------------------------
#routine compute_ready_list
#    ??@root_obj cpu
#    ??@prio_map  prio_for_process
#    ??@prio_map  prio_for_rez
#    ??@uint      max_task_prio
#    ??@uint      max_prio
#    !@prio_list ready_list
#:
#    ready_list := [@prio_list emptyList];
#
#    # put in the list the fifo for the idle task
#    ready_list += !1;
#
#    # begin at priority 1
#    @uint prio := 1;
#
#    loop max_prio :
#    while prio <= max_prio do
#        # look for objects with the same priority
#        @uint fifosize := 0;
#        foreach prio_for_process (@lstring n @uint p @uint a) do
#            if p == prio then fifosize := fifosize + a; end if;
#        end foreach;
#        foreach prio_for_rez (@lstring n @uint p @uint a) do
#            if p == prio then fifosize := fifosize + a; end if;
#        end foreach;
#
#        if prio == max_task_prio then
#          # Since any task can get the RES_SCHEDULER resource,
#          # This priority level should have one more entry
#          fifosize++;
#          # And add the tasks used to execute the shutdown and
#          # startup hooks of OS Applications
#          foreach [cpu applis] do
#            cast [app startuphook]:
#            when == @bool_class sh do if [sh value] then fifosize++; end if;
#            else end cast;
#            cast [app shutdownhook]:
#            when == @bool_class sh do if [sh value] then fifosize++; end if;
#            else end cast;
#          end foreach;
#        end if;
#
#        ready_list += !fifosize;
#        prio++;
#    end loop;
#
##    log ready_list;
#end routine;

#routine compute_app_for_obj
#  ?@app_map    apps
#  !@stringMap app_for_obj
#:
#  app_for_obj := [@stringMap emptyMap];
#
#  foreach apps do
#    @string app_name := [lkey string];
#    foreach [app tasks] do
#      if not [app_for_obj hasKey ![lkey string]] then
#        [!?app_for_obj put !lkey !app_name];
#      end if;
#    end foreach;
#    foreach [app isrs] do
#      if not [app_for_obj hasKey ![lkey string]] then
#        [!?app_for_obj put !lkey !app_name];
#      end if;
#    end foreach;
#    foreach [app alarms] do
#      if not [app_for_obj hasKey ![lkey string]] then
#        [!?app_for_obj put !lkey !app_name];
#      end if;
#    end foreach;
#    foreach [app scheduletables] do
#      if not [app_for_obj hasKey ![lkey string]] then
#        [!?app_for_obj put !lkey !app_name];
#      end if;
#    end foreach;
#    foreach [app counters] do
#      if not [app_for_obj hasKey ![lkey string]] then
#        [!?app_for_obj put !lkey !app_name];
#      end if;
#    end foreach;
#    foreach [app resources] do
#      if not [app_for_obj hasKey ![lkey string]] then
#        [!?app_for_obj put !lkey !app_name];
#      end if;
#    end foreach;
#  end foreach;
##  [!?app_for_obj put ![@lstring new !"SystemCounter" !here] !"INVALID_OSAPPLICATION"];
#
#end routine;


#routine computeEvents
#  ??@root_obj cpu
#  !@eventMaskMap eventMasks
#:
#  eventMasks := [@eventMaskMap emptyMap];
#  @task_mask maskForTask := [@task_mask emptyMap];
#  #
#  # Iterate through the tasks to get the events used.
#  # A or is done with the user masks of user events
#  # and the result is stored in the task_mask map.
#  # event conflicts are detected when building this Or
#  # Auto events are looked up and for each auto event
#  # the number of referencing tasks is computed and
#  # stored in the evt_usage map.
#  # In addition, a reverse mapping allowing to get all
#  # the task referencing an event is built and stored
#  # in the tasks_for_event map.
#  #
#  @event_usage_map evt_usage [emptyMap];
#  @stringset_map tasksForEvent [emptyMap]; 
#
#  foreach [cpu tasks] (@lstring taskName @task_obj task) do
#    @lstringlist events_used;
#    events_used := [task events];
#    @uint64 event_mask := 0L;
#    foreach events_used (@lstring evt_name) do
#      @event_obj event;
#      [[cpu events] get !evt_name ?event];
#      @event_mask_obj mask_obj;
#      mask_obj := [event mask];
#      cast mask_obj :
#      when == @event_mask_user_obj emu do
#        if (event_mask & [[emu mask] uint64]) == 0L then
#          event_mask := event_mask | [[emu mask] uint64];
#        else
#          error [emu mask] : "MASK of event ".[evt_name string]." conflicts with previous declarations";
#        end if;
#      when == @event_mask_auto_obj do
#        @uint count;
#        if [evt_usage hasKey ![evt_name string]] then
#          [!?evt_usage delete !evt_name ?count];
#          count++;
#        else
#          count := 1;
#        end if;
#        [!?evt_usage insert_count !evt_name !count];
#      else
#      end cast;
#      @stringset refTasks [emptySet];
#      if [tasksForEvent hasKey ![evt_name string]] then
#        [!?tasksForEvent delete !evt_name ?refTasks];
#      end if;
#      refTasks += ![taskName string];
#      [!?tasksForEvent add !evt_name !refTasks];
#    end foreach;
#    [!?maskForTask insert_mask !taskName !event_mask];
#  end foreach;
#
#  #
#  # Output the user events
#  #
#  foreach [cpu events] (@lstring eventName @event_obj event) do
#    @event_mask_obj eventMask := [event mask];
#    cast eventMask :
#    when == @event_mask_user_obj emu do
#      [!?eventMasks put !eventName ![[emu mask] uint64]];
#    else end cast;
#  end foreach;
#
#  #
#  # Sort the auto events by reference count
#  # in a sorted list
#  #
#  @sorted_events sortedEvents [emptySortedList];
#  foreach evt_usage do
#    sortedEvents += !lkey !count;
#  end foreach;
#
#  #
#  # Compute the mask for auto events
#  #
#  # iterate through the events to compute the mask
#  #
#
#  foreach sortedEvents do
#    if [tasksForEvent hasKey ![event_name string]] then
#      @stringset tasks;
#      [tasksForEvent get !event_name ?tasks];
#      @uint64 mask := 0L;
#      foreach tasks (@string t) do
#        @uint64 tm;
#        [maskForTask get_mask ![@lstring new !t !here] ?tm];
#        mask := mask | tm;
#      end foreach;
#      # 0 are available slots in the mask.
#      # look for the first available slot starting from bit 0
#      @uint64 bit := 1L;
#      loop 32 :
#      while (bit < [[@uint max] uint64]) & ((bit & ~mask) == 0L) do
#        bit := bit << 1; 
#      end loop;
#      if bit > [[@uint max] uint64] then
#        error event_name : "All event mask bits are already use, event ".[event_name string]." can't be created";
#      end if;
#      # message "found ".[bit string]."\n";
#      # update the task mask of the corresponding tasks
#      foreach tasks (@string t) do
#        @uint64 tm;
#        [!?maskForTask del_mask ![@lstring new !t !here] ?tm];
#        tm := bit | tm;
#        [!?maskForTask insert_mask ![@lstring new !t !here] !tm];
#      end foreach;
#     
#      [!?eventMasks put !event_name !bit];
##      @string result := tpl_event;
##      doReplace !?result !"$EVENT$" ![event_name string]."_mask";
##      doReplace !?result !"$EVENT_NAME$" ![event_name string];
##      doReplace !?result !"$EVENT_MASK$" ![bit string];
##      imp_result .= result."\n";
#
#    end if;
#  end foreach;
#end routine;

class @goilContext : @gtlContext {}

override getter @goilContext fullPrefix
  ?let @gtlData vars
  ->@lstring full
{
  @string stringPrefix = prefix
  if "compiler" == stringPrefix then
    [vars structField !lstring(!"COMPILER") ?let @gtlData compiler ?*]
    stringPrefix += "/" + (compiler as @gtlString)
  elsif "linker" == stringPrefix then
    [vars structField !lstring(!"LINKER") ?let @gtlData linker ?*]
    stringPrefix += "/" + (linker as @gtlString)
  elsif "assembler" == stringPrefix then
    [vars structField !lstring(!"ASSEMBLER") ?let @gtlData assembler ?*]
    stringPrefix += "/" + (assembler as @gtlString)
  elsif stringPrefix == "ROOT" then
    stringPrefix = ""
  end
  full = .new { !stringPrefix !prefix }
#  message "%%%% Return : " + full + "\n"
}

func emptyGoilContext
  ->@goilContext context 
{
  context = .new {
    !emptylstring()
    !""
    !""
    !""
    !".gtl"
    !.emptyList
    !.emptyList
    !true
    !defaultDebugSettings()
  }
}

##
# @fn generate_all
#
# routine generate_all does the generation of all the system.
# 
# @param cpu the root object got from the parsing
# 
proc generate_all
  ?@gtlData cfg
{
  # code generation
#  @string timestamp := [@string stringWithCurrentDateTime];

  let @string temp_dir = templates_directory(!"code")

  if temp_dir != "" then
      let @string target = [option goil_options.target_platform value]
      if target != "" then

          @gtlContext context = emptyGoilContext()
          [!?context setTemplateDirectory
            !templates_directory(!"")
          ]
          [!?context setUserTemplateDirectory
            ![@string.stringWithSourceFilePath stringByDeletingLastPathComponent]
             + "/templates"
          ]
          [!?context setTemplateExtension
            !"goilTemplate"
          ]
          [!?context addModulePath
            !templates_directory(!"")
            !"lib"
          ]
          [!?context setPath
            ![option goil_options.target_platform value]
          ]
                    
          let @string  goilLog = invokeGTL (
            !@gtlString.new { 
                !.here 
                !lstring(!"root template filename") 
                ![option goil_options.root value]
             }
            !context
            !cfg
          )
          
          if [option goil_options.generate_log value] then
            [goilLog writeToFile !"goil.log"]
          end
          
      else
          message "No target platform given, compiling aborted\n"
      end
  else
      message "No template directory defined, compiling aborted\n"
  end
}