# A few helpful tips about the Rules file:
#
# * The string given to #compile and #route are matching patterns for
#   identifiers--not for paths. Therefore, you can’t match on extension.
#
# * The order of rules is important: for each item, only the first matching
#   rule is applied.
#
# * Item identifiers start and end with a slash (e.g. “/about/” for the file
#   “content/about.html”). To select all children, grandchildren, … of an
#   item, use the pattern “/about/*/”; “/about/*” will also select the parent,
#   because “*” matches zero or more characters.

# ignore everything starting with underscore
ignore '/**/_*'

preprocess do
   NOFACTION = "Factionless"

   # create name -> item mappings
   config[:spob] = {}
   @items.find_all('/spob/*.md').each do |s|
      s[:name] = s[:spob][:"+@name"]
      config[:spob][s[:name]] = s
      s[:tags] = []
   end
   config[:ssys] = {}
   @items.find_all("/ssys/*.md").each do |s|
      s[:name] = s[:ssys][:"+@name"]
      config[:ssys][s[:name]] = s
      s[:tags] = []
   end
   config[:misn] = {}
   @items.find_all("/misn/**/*.lua").each do |m|
      m[:name] = m[:mission][:"+@name"]
      config[:misn][m[:name]] = m
   end
   config[:evts] = {}
   @items.find_all("/evts/**/*.lua").each do |e|
      e[:name] = e[:event][:"+@name"]
      config[:evts][e[:name]] = e
   end
   config[:fcts] = {}
   @items.find_all("/fcts/*.md").each do |f|
      f[:name] = f[:faction][:"+@name"]
      config[:fcts][f[:name]] = f
   end
   config[:npcs] = {}
   @items.find_all("/npcs/*.md").each do |n|
      config[:npcs][n[:name]] = n
   end

   # Helper functions
   def find_lines( matchitems, matchstr, tag, storeval=nil )
      if storeval.nil?
         storeval = matchstr
      end
      re = /\b#{matchstr}\b/
      lst = []
      @items.find_all( matchitems ).each do |m|
         linenum = []
         flines = File.readlines(m.raw_filename)
         flines.each_with_index do |l,i|
            if l =~ re
               li = [0,i-2].max
               ui = [flines.length()-1,i+2].min
               (li..ui).each do |j|
                  linenum.push(j)
               end

               if m[tag].nil?
                  m[tag] = []
               end
               m[tag].push( storeval )
            end
         end
         lines = []
         linenum.uniq.sort.each do |i|
            s = "<span class='text-secondary'>#{i.to_s.rjust(3,'0')}:</span> #{html_escape(flines[i])}"
            s.gsub! matchstr, "<span class='text-primary'>#{matchstr}</span>"
            lines.push(s)
         end
         if lines.length() > 0
            lst.push( {name: m[:name], lines: lines } )
         end
      end
      return lst
   end

   def colnaev2html( c )
      return c.downcase
   end

   # Set up factions
   @items.find_all("/fcts/*.md").each do |f|
      f[:id] =  Base64.encode64( f[:name] ).strip
      f[:tags] = []

      # Optional base stuff
      if f[:faction][:colour].nil?
         f[:colour] = "none"
      else
         f[:colour] = colnaev2html( f[:faction][:colour] )
      end
      if f[:faction][:longname].nil?
         f[:longname] = f[:name]
      else
         f[:longname] = f[:faction][:longname]
      end
      if f[:faction][:description].nil?
         f[:description] = "This faction has no description."
      else
         f[:description] = f[:faction][:description]
      end

      # Get logo
      if not f[:faction][:logo].nil?
         f[:logo] = "/gfx/logo/#{f[:faction][:logo]}.webp"
      end

      # Add tags
      if not f[:faction][:tags].nil?
         f[:tags] += Array(f[:faction][:tags][:tag])
      end

      # Mark as spoiler if not known
      if not f[:faction].key?(:known) or f[:faction].key?(:invisible)
         f[:tags].push("spoiler")
      end
   end

   # Mappings from spob to system and system to array of spobs.
   spobssys = {}
   ssysspob = {}

   # Process systems
   @items.find_all('/ssys/*.md').each do |s|
      # Core attributes
      s[:id] =  Base64.encode64( s[:name] ).strip

      # Set some other attributes
      s[:radius] = s[:ssys][:general][:radius]
      s[:spacedust] = s[:ssys][:general][:spacedust]
      s[:interference] = s[:ssys][:general][:interference]
      s[:x] = s[:ssys][:pos][:"+@x"]
      s[:y] = s[:ssys][:pos][:"+@y"]

      # Add tags
      if not s[:ssys][:tags].nil?
         s[:tags] += Array(s[:ssys][:tags][:tag])
      end

      # Add jumps
      if not s[:ssys][:jumps].nil? then
         if s[:ssys][:jumps][:jump].kind_of?(Array) then
            jumps = s[:ssys][:jumps][:jump]
         else
            jumps = [s[:ssys][:jumps][:jump]]
         end
         s[:jumps] = []
         jumps.each do |jmp|
            s[:jumps].push( {target: jmp[:"+@target"], hidden: jmp.key?(:hidden) } )
         end
         s[:jumps].sort_by{ |j| j[:target] }
      else
         s[:jumps] = []
      end

      # Add asteroids
      if not s[:ssys][:asteroids].nil? then
         s[:tags].push( "asteroids" )
      end

      # Map spobs to systems
      spb = s[:ssys][:spobs]
      if not spb.nil? and not spb[:spob].nil?
         s[:spobs] = []
         ssysspob[ s[:name] ] = []
         Array(spb[:spob]).each do |p|
            spobssys[p] = s[:name]
            ssysspob[ s[:name] ].push( p )
            s[:spobs].push( p )
         end
         s[:spobs].sort()
      else
         s[:spobs] = []
      end

      # Try to find missions it's in
      s[:missions] = find_lines('/misn/**/*.lua', s[:name], :ssys)
      if s[:missions].length() > 0
         s[:tags].push("inmission")
      end
      s[:events] = find_lines('/evts/**/*.lua', s[:name], :ssys)
      if s[:events].length() > 0
         s[:tags].push("inevent")
      end
      if s[:tags].length()<=0
         s[:tags].push("none")
      end
   end

   @items.find_all('/spob/*.md').each do |s|
      # Core attributes
      s[:id] =  Base64.encode64( s[:name] ).strip

      # Set some other atrtibutes
      if not s[:spob][:general][:description].nil? then
         s[:description] = s[:spob][:general][:description]
      end
      if not s[:spob][:general][:bar].nil? then
         s[:bar] = s[:spob][:general][:bar]
      end
      if not s[:spob][:general][:population].nil? then
         s[:population] = s[:spob][:general][:population].to_f.to_i.to_s
      else
         s[:population] = 0
      end
      if not s[:spob][:presence].nil? and not s[:spob][:presence][:faction].nil?
         s[:faction] = s[:spob][:presence][:faction]
         # Propagate faction spoiler status
         fct = faction_get( s[:faction] )
         if not fct.nil? and fct[:tags].include? "spoiler"
            s[:tags].push("spoiler")
         end
      else
         s[:faction] = NOFACTION
      end
      s[:spobclass] = s[:spob][:general][:class]
      if not s[:spob][:general][:services].nil?
         s[:services] = s[:spob][:general][:services].keys
      else
         s[:services] = []
      end
      if not s[:spob][:tags].nil?
         s[:tags] += Array(s[:spob][:tags][:tag])
      end

      # See if in system
      if not spobssys[ s[:name] ].nil? then
         s[:ssys] = spobssys[ s[:name] ]
         sys = ssys_get( s[:ssys] )

         # Get neighbours
         s[:neighbours] = []
         sys[:spobs].each do |n|
            if n!=s[:name] then
               s[:neighbours].push(n)
            end
         end
         s[:neighbours].sort()
      else
         s[:neighbours] = []
      end

      # Try to find missions it's in
      s[:missions] = find_lines('/misn/**/*.lua', s[:name], :spob)
      if s[:missions].length() > 0
         s[:tags].push("inmission")
      end
      s[:events] = find_lines('/evts/**/*.lua', s[:name], :spob)
      if s[:events].length() > 0
         s[:tags].push("inevent")
      end
      if s[:tags].length()<=0
         s[:tags].push("none")
      end
   end

   # Second pass to set up system details that need spob details
   @items.find_all('/ssys/*.md').each do |s|
      s[:factions] = Set[]
      # Systems with spoiler spobs are spoilers
      s[:spobs].each do |si|
         spb = spob_get( si )
         if spb[:tags].include? "spoiler" then
            s[:tags].push("spoiler")
         end
      end
      s[:spobs].each do |si|
         spb = spob_get( si )
         if spb[:faction] != NOFACTION then
            s[:factions].add( spb[:faction] )
         end
         # Propagate system spoiler status to spobs
         if s[:tags].include? "spoiler"
            spb[:tags].push("spoiler")
         end
      end
      s[:factions] = Array(s[:factions]).sort()
   end

   # Set up NPCs
   @items.find_all('/npcs/*.md').each do |n|
      n[:id] = Base64.encode64( n[:name] ).strip
      n[:missions] = find_lines('/misn/**/*.lua', n[:func], :npcs, n[:name])
      n[:events] = find_lines('/evts/**/*.lua', n[:func], :npcs, n[:name])
   end

   # Set up missions and events
   @items.find_all('/misn/**/*.lua').each do |m|
      if m[:tags].nil?
         m[:tags] = []
      end
      if not m[:mission][:notes].nil? and not m[:mission][:notes][:campaign].nil? then
         m[:campaign] = m[:mission][:notes][:campaign]
      end
      if m[:spob].nil?
         m[:spob] = []
      end
      if m[:ssys].nil?
         m[:ssys] = []
      end
      if m[:npcs].nil?
         m[:npcs] = []
      end
   end
   @items.find_all('/evts/**/*.lua').each do |e|
      if e[:tags].nil?
         e[:tags] = []
      end
      if not e[:event][:notes].nil? and not e[:event][:notes][:campaign].nil? then
         e[:campaign] = e[:event][:notes][:campaign]
      end
      if e[:spob].nil?
         e[:spob] = []
      end
      if e[:ssys].nil?
         e[:ssys] = []
      end
      if e[:npcs].nil?
         e[:npcs] = []
      end
   end

   # Find missions / events in the campaign
   @items.find_all('/cmpn/**/*.md').each do |c|
      c[:id] =  Base64.encode64( c[:name] ).strip
      tag = c[:name]
      c[:ssys] = []
      c[:spob] = []
      c[:npcs] = []
      c[:missions] = []
      @items.find_all('/misn/**/*.lua').each do |m|
         if not m[:campaign].nil? and m[:campaign]==tag then
            c[:missions].push( m )
            m[:ssys].each do |s|
               c[:ssys].push( s )
            end
            m[:spob].each do |s|
               c[:spob].push( s )
            end
            m[:npcs].each do |n|
               c[:npcs].push( n )
            end
         end
      end
      c[:missions].sort_by{ |m| m[:name] }
      c[:events] = []
      @items.find_all('/evts/**/*.lua').each do |e|
         if not e[:campaign].nil? and e[:campaign]==tag then
            c[:events].push( e )
            e[:ssys].each do |s|
               c[:ssys].push( s )
            end
            e[:spob].each do |s|
               c[:spob].push( s )
            end
            e[:npcs].each do |n|
               c[:npcs].push( n )
            end
         end
      end
      c[:events].sort_by{ |e| e[:name] }

      c[:spob] = c[:spob].uniq.sort
      c[:ssys] = c[:ssys].uniq.sort
      c[:npcs] = c[:npcs].uniq.sort
   end

   # Dedup tags
   @items.find_all('/spob/*.md').each do |s|
      s[:tags] = s[:tags].uniq.sort
   end
   @items.find_all('/ssys/*.md').each do |s|
      s[:tags] = s[:tags].uniq.sort
   end
end

# Some special files
compile '/sitemap.rb' do
   filter :erb
   write '/sitemap.xml'
end
compile '/robots.txt' do
   filter :erb
   write '/robots.txt'
end

compile '/spob/*.xml' do
   filter :x
end

# favoicon
[16, 32, 64, 128, 180, 196].each do |s|
   compile '/favicon.png', rep: "s#{s.to_s}" do
      filter :thumbnailize, :width => s, :height => s
      write "/favicon-#{s.to_s}.png"
   end
end
compile '/favicon.png', rep: :ico do
   filter :thumbnailize, :width => 48, :height => 48, :filetype => 'ico'
   write '/favicon.ico'
end

compile '/**/*.md' do
  if item['hidden'].nil?
    filter :erb

    # Markdown
    filter :kramdown

    # Standard procesing
    layout '/default.html'
    filter :relativize_paths, :type => :css
    filter :relativize_paths, :type => :html
    filter :external, exec: 'tidy', options: %w(--quiet yes --indent auto --indent-spaces 2 --wrap 100 --tidy-mark no --char-encoding utf8 --add-meta-charset yes --drop-empty-elements no --warn-proprietary-attributes no)

    basename = (item.identifier=='/index.md') ? '' : item.identifier.without_ext
    write basename+'/index.html'
  end
end

# Javascript and friends
compile '/**/*.js' do
   filter :concat_js
   filter :uglify_js, :comments => :none
   write ext: 'js'
end

# CSS and friends
compile '**/*.{css,scss}' do
   filter :erb
   filter :dart_sass, syntax: :scss if item[:extension] == 'scss'
   filter :rainpress
   write ext: 'css'
end

compile "gfx/logo/*.webp", rep: :thumbnail do
   filter :thumbnailize, :width => 64, :height => 64, :opt => true
   write item.identifier.without_ext + '_tn.png' # all png for transparency if cropped
end

# Other objects
compile '/**/*' do
   write item.identifier.to_s
end

layout '/**/*', :erb
