#!/usr/bin/ruby

#    (c) 2005-2006 Frederik Deweerdt frederik.deweerdt@gmail.com

#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

require 'net/ftp'

class RGBColor
  def initialize(rgb, factor=0.9) 
    @r = Float((rgb & 0xff0000) >> 16)
    @g = Float((rgb & 0x00ff00) >> 8)
    @b = Float((rgb & 0x0000ff))

    @factor = Float(factor)
  end#def initialize

  def lighten!
    @r +=1; @g += 1; @b += 1
    @r = (@r / @factor) % 255
    @g = (@g / @factor) % 255
    @b = (@b / @factor) % 255
    return self
  end#def lighten!

  def darken!
    @r -=1; @g -= 1; @b -= 1
    @r = (@r * @factor) % 255
    @g = (@g * @factor) % 255
    @b = (@b * @factor) % 255
    return self
  end#def darken!

  def to_s
    return sprintf("%02x%02x%02x", @r.to_i, @g.to_i, @b.to_i);
  end#def to_s
end#class RGBColor

class Rectangle
  attr_writer :width,:height,:area
  attr_reader :width,:height,:area,:news_topic
  def initialize (width, height, news_topic)
    @width = Float(width)
    @height = Float(height)
    @news_topic = news_topic
  end#intialize

  def area
    return @width * @height
  end#def area

  def ratio
    ret = @width / @height
    ret = 1 / ret if ret < 1
    return ret
  end#def ratio

  def to_s
    return "[#{@width},#{@height},@#{area},r#{ratio}]"
  end#to_s
end#class Rectangle

class Row
  attr_reader :kind,:elements
  HORIZ = 0
  VERT = 1 
  def initialize(kind,length)
    @kind = kind
    @fixed_side_length = length
    @elements = Array.new
    @ratio = 9999
  end#initialize

  def area
    area = 0.0
    @elements.each { |r| area += r.area }
    return area
  end
  
  def width
    return area / @fixed_side_length if @kind == VERT
    return @fixed_side_length
  end#width

  def height
    return area / @fixed_side_length if @kind == HORIZ
    return @fixed_side_length
  end#height

  def recalculate_layout
    #puts "#{area} / #{@fixed_side_length}"
    variable_side_lenth = area / @fixed_side_length
    new_elem = Array.new
    if @kind == VERT
    then
      @elements.each { |r| new_elem.push(Rectangle.new(variable_side_lenth, r.area / variable_side_lenth,r.news_topic)) }
    else
      @elements.each { |r| new_elem.push(Rectangle.new(r.area / variable_side_lenth, variable_side_lenth, r.news_topic)) }
    end#if

    @elements = new_elem
  end#recalculate_layout

  public
  def push(o)
    @elements.push(o)
    recalculate_layout
    better = false
    better = true if @elements.last.ratio < @ratio
    @ratio = @elements.last.ratio
    return better
  end#push

  def pop()
    o = @elements.pop()
    recalculate_layout
    return o
  end#pop

  def to_s
    ret = String.new
    ret += "=====\n"
    @elements.each { |r| ret +=  "#{r.to_s}\n" }
    ret += "-----\n"
  end#dump_row
end#class Row

class Frame
  def initialize(org_x, org_y, width, height)
    @org_x = org_x
    @org_y = org_y
    @width = width
    @height = height
    @free_x = 0
    @free_y = 0
    @current_row = free_width > free_height ? Row.new(Row::VERT, free_height) : Row.new(Row::HORIZ,free_width)
    @rows = Array.new
  end#initialize

  def free_width
    @width - @free_x
  end#free_width

  def free_height
    @height - @free_y
  end#free_width

  def add(area, nt)
    r = Rectangle.new(area, 1, nt)

    if not @current_row.push(r)
    then
      @current_row.pop()
      @rows.push(@current_row)
      @free_x += @current_row.width if @current_row.kind == Row::VERT
      @free_y += @current_row.height if @current_row.kind == Row::HORIZ
      @current_row = free_width > free_height ? Row.new(Row::VERT, free_height) : Row.new(Row::HORIZ,free_width)
      @current_row.push(r)
    end#if
  end#add

  def to_s
    @rows.each do
      |row|
      puts row
    end#each
    puts @current_row
  end#to_s

  public
  def to_html(colors_by_topic)
    s = ""
    pencil_x = @org_x
    pencil_y = @org_y
    previous_pencil_x = pencil_x
    previous_pencil_y = pencil_y
    zoom = 1
    border = 0
    @rows.push(@current_row)

    @rows.each do
      |row|
      previous_pencil_x = pencil_x
      previous_pencil_y = pencil_y
      row.elements.each do
        |r|
        color = colors_by_topic[r.news_topic.category];
        colors_by_topic[r.news_topic.category] = color.darken! 

        news_title = r.news_topic.text.gsub(/"/,'&quot;').gsub(/\[\d*\]/, '')
        font_size = Integer(Math.log(pus(r.area,6) / r.news_topic.title.length)/6)

        s += "<div class=\"news\" title=\"#{news_title}\" style=\"position:absolute;left:#{pencil_x}px;top:#{pencil_y}px;background-color: ##{color.to_s};height:#{Integer(r.height*zoom)}px;width:#{Integer(r.width*zoom)}px;overflow:hidden;font-size:#{font_size}pt\">"
        
        s += "<p class=\"news\"><a class=\"news\" href=\"#{r.news_topic.title_link.gsub(/&/,'&amp;')}\">#{r.news_topic.title}</a><a class=\"news-more\" href=\"#{r.news_topic.related_link.gsub(/&/,'&amp;')}\" title=\"articles reli&eacute;s\"> (#{r.news_topic.related_count}&nbsp;articles...)</a></p>" if r.news_topic.title_link != nil

        s += "<center><a href=\"#{r.news_topic.image_link.gsub(/&/,'&amp;')}\" title=\"\"><img border=\"0\" alt=\"News image\" class=\"news\" src=\"#{r.news_topic.image_url.gsub(/&/,'&amp;').gsub(/afp\.com/,'example.com').gsub(/afp\.fr/,'example.com')}\"></a></center>" if r.news_topic.image_url != nil

        s+= "</div>\n"

        pencil_y += Integer(r.height*zoom) + border if row.kind == Row::VERT
        pencil_x += Integer(r.width*zoom) + border if row.kind == Row::HORIZ
      end#do row.elements.each
      if row.kind == Row::VERT  && row.elements.last != nil
      then
	pencil_x += Integer(row.elements.last.width * zoom) + border
	pencil_y = previous_pencil_y
      end#if
      if row.kind == Row::HORIZ && row.elements.last != nil
      then
	pencil_y += Integer(row.elements.last.height * zoom) + border
	pencil_x = previous_pencil_x 
      end#if
    end#each
    @rows.pop()
    return s
  end#to_html
  def pus (int, pow)
    res = int
    while pow > 1 do
      res *= int
      pow -= 1
    end#while
    return res	
  end#pus
end#class Frame

class NewsLang
  private

  def initialize(end_of_gn, end_of_article, in_the_news, related_articles, topic_separator, \
                 top_stories, more_top_stories, command, customize_begin, customize_end)
    @end_of_gn = end_of_gn
    @end_of_article = end_of_article
    @in_the_news = in_the_news
    @related_articles = related_articles
    @topic_separator = topic_separator
    @top_stories = top_stories
    @more_top_stories = more_top_stories
    @command = command
    @customize_begin = customize_begin
    @customize_end = customize_end
  end#def initialize

  public
  attr_reader :end_of_gn, :related_articles,:end_of_article,:in_the_news,:top_stories,:command, :topic_separator, :more_top_stories, :customize_begin, :customize_end;

  FR = NewsLang.new("Versions  internationales de Google Actualités disponibles", \
                    "articles connexes", \
                    "Google Actualit.s", \
                    '\[(\d+)\]et (\d+) articles connexes', \
                    '(\[\d+\]([\w|\/|\(|\)| ]+)\s\s>>)|(la une \(suite\))', \
                    "À la une", \
                    ". la une .suite.", \
                    '|lynx  -cfg /dev/null -width 2000 -image_links  -dump  http://news.google.com/news?ned=fr', \
		    #'|wget --user-agent=Mozilla -O /dev/stdout http://news.google.com/news?ned=fr 2> /dev/null|lynx -width 200 -image_links -stdin -dump|iconv -c -f ISO_8859-1 -t UTF-8', \
                    #'|wget --user-agent=Mozilla -O /dev/stdout http://news.google.fr/news?ned=fr 2> /dev/null|lynx -width 200 -image_links -stdin -dump', \
                    #'|cat fr.txt', \
                    'Personnaliser cette page', \
                    'Personnaliser cette page fermer');

  EN = NewsLang.new("International versions of Google News available in", \
		    'related', \
		    "Google News", \
		    '\[(\d+)\]all ([\d|,]+) related', \
		    '(\[\d+\]([\w|\/|\(|\)| |\.]+)\s\s»)|(More Top Stories)', \
		    'Top Stories', \
		    'More Top Stories', \
                    '|lynx  -cfg /dev/null -width 2000 -image_links  -dump  http://news.google.com/news', \
                    'Customize this page', \
                    'Customize this page close');

  #  ES = NewsLang.new("Versiones internacionales de Google Noticias disponibles en",
  #                          "culos relacionados",
  #                          "Es noticia",
  #                          '\[(\d+)\]y (\d+) art.culos relacionados',
  #                          '(\[\d+\]([\w|\/|\(|\)| ]+)\s\s>>)|(noticias destacadas)');

end#class NewsLang

class NewsPage 
  attr_reader :articles,:no_articles,:display_as_text,:no_articles_by_topic,:articles_by_topic,:topics
  LinesToSkip = 35

  def initialize(lines, limit=0, ns=NewsLang::EN)
    end_of_article = false
    skip=LinesToSkip
    topic = ""
    @articles = Array.new
    @no_articles = 0
    @links = Array.new
    @topics = Array.new
    @ns = ns
    category = @ns.top_stories

    lines.each do
      |line|
      if line =~ /(\d+)\. (http:\/\/.*)/
      then
        @links[$1.to_i] = $2
      end#if
    end#each


    image_index = nil
    image_url_index = nil
    in_the_news = false

    in_customize_html = false
    expecting_category = true
    lines.each {
      |line|

      in_the_news = true if line =~ /#{ns.in_the_news}/

        next if not in_the_news

      in_customize_html = true if line =~ /#{ns.customize_begin}/
        in_customize_html = false if line =~ /#{ns.customize_end}/
        
        next if in_customize_html

      # Category?
      if line =~ /cleardot\.gif/
      then
        expecting_category = true 
        next
      end#if

      # Category found
      if expecting_category and in_the_news
      then
        expecting_category = false
        if line =~ /\[\d+\]([\w|\/| ]+)  / or line =~ /(#{ns.more_top_stories})/
        then
          category = $1 
        end#if
        next
      end#if

      # Article found
      if line =~ /#{ns.related_articles}/
      then
        related_link = @links[$1.to_i]
        related_count = $2.strip.gsub(',','').to_i
        art_line_array = Array.new
        tmp_image_line_array = Array.new
        search_for_image = false
        image_url = nil
        image_link = nil

        lines.slice(0,lines.index(line)+1).reverse.each {
          |l|
          search_for_image = true if not search_for_image and l.strip.empty?

          # Search the image
          if search_for_image
          then
            tmp_image_line_array.push(l)
            break if tmp_image_line_array.size > 3
            if l =~ /\[(.*)\]\[.*\]-\[(.*)\]\[.*\]/
            then
              image_link = @links[$1.to_i]
              image_url = @links[$2.to_i]
              break;
            end#if
          else
            art_line_array.push(l.strip)
          end#if
        }
        art_line_array.reverse[0] =~ /\[(\d+)\](.*)/
        title = $2.strip unless $2 == nil
        title_link = @links[$1.to_i]
        text = art_line_array.reverse.grep(/^[^\[]/).slice(1,4).join

        nt = NewsTopic.new(title, title_link, text, image_url, image_link, related_link, related_count, category)
        @articles.push(nt)
        @no_articles += nt.related_count
      end#if
    }

    #
    # Assign percentages
    #
    articles.each do
      |art|
      art.pct = art.related_count * 100 / @no_articles
    end#do
    #
    # Sort articles by topic
    #
    @articles_by_topic = Hash.new
    @no_articles_by_topic = Hash.new
    articles.each do
      |art|
      if not @articles_by_topic.has_key?(art.category)
      then
        @articles_by_topic[art.category] = Array.new
        @no_articles_by_topic[art.category] = 0
        @topics.push(art.category)
      end#if
      @articles_by_topic[art.category].push(art)
      @no_articles_by_topic[art.category] += art.related_count
    end#do

  end#initialize

  def display_as_text
    pct = 0.0
    articles.sort.each {
      |art|
      pct += art.no_articles.to_f * 100.0 / @no_articles.to_f
      #puts "#{art.no_articles.to_f * 100.0 / @no_articles.to_f}%"
      puts art.to_s
    }
  end#display_as_text

end#class NewsPage

class NewsTopic

  attr_reader :title, :title_link, :text, :image_url, :image_link, :related_link, :related_count, :category, :pct
  attr_writer :pct
  def initialize(title, title_link, text, image_url, image_link, related_link, related_count, category)
    @title = title
    @title_link = title_link
    @text = text
    @image_url = image_url
    @image_link = image_link
    @related_link = related_link
    @related_count = related_count
    @category = category
    @pct = nil
  end#initialize

  def <=>(nt)
    return @related_count <=> nt.related_count
    if @category != nt.category
    then
      return @category <=> nt.category
    else
      return @related_count <=> nt.related_count
    end#if
  end# <=>

  def to_s 
    return "*#{@title}*\t #{@category}\t #{@related_count}\n#{@text}"
  end#to_s
end#class NewsTopic

def generate_html(fileout, lang, limit)
  file = Kernel.open(lang.command)

  np = NewsPage.new(file.readlines, limit, lang)
  file.close

  colors = Array.new

  colors.push(RGBColor.new(0xffcc11))
  colors.push(RGBColor.new(0x111188))
  colors.push(RGBColor.new(0x118111))
  colors.push(RGBColor.new(0xcc11111))
  colors.push(RGBColor.new(0xff6611))
  colors.push(RGBColor.new(0x663399))
  colors.push(RGBColor.new(0xcccccc))
  colors.push(RGBColor.new(0x669999))
  colors.push(RGBColor.new(0xfedcba))

  coltop = Hash.new
  i = 0
  np.topics.each {
    |t|
    coltop[t] = colors[i]
    i += 1
  }

  fileout.puts <<EOF
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd">
    <html><head>
    <title>Open News Map</title>
<META HTTP-EQUIV=\"CONTENT-Type\" CONTENT=\"text/html;CHARSET=ISO-8859-1\">
<style type="text/css">
@import url('style.css');
</style>
</head>
<body>
<img style="position:absolute;left:140px;top:30px;" src="onm_logo.jpg" alt="logo"/>
<table style="position:absolute;left:15;top:50px;"><tr><td><ul>
<li style="margin: 0pt; padding: 0pt; background-color: white;"><p style="text-decoration: underline;font-weight:bold;" class="category">Legend</p></li>
EOF

coltop.each_key { |topic| fileout.puts "<li style=\"margin: 0pt; padding: 0pt; background-color: ##{coltop[topic]};\"><p class=\"category\">#{topic}</p></li>" }

fileout.puts <<EOF
</ul></td></tr>
<tr><td valign="top"><ul>
<li style="margin: 0pt; padding: 0pt; background-color: white;"><p style="text-decoration: underline;font-weight:bold;" class="category">Language</p></li>
<li style="margin: 0pt; padding: 0pt; background-color: #abcdef;"><p class="category"><a href="index_fr.html">french</a></p></li>
<li style="margin: 0pt; padding: 0pt; background-color: #cdefab;"><p class="category"><a href="index.html">english</a></p></li>
</ul></td></tr>
<tr><td valign="top">
<ul><li style="margin: 0pt; padding: 0pt; background-color: white;"><p style="text-decoration: underline;font-weight:bold;" class="category"><a onclick="document.getElementById('help').style.display = 'block';" href="#">Help</a></p></li>
<li style="margin: 0pt; padding: 0pt; background-color: white;"><p style="text-decoration: underline;font-weight:bold;" class="category"><a href="about.html">About</a></p></li>
<li style="margin: 0pt; padding: 0pt; background-color: white;"><p style="text-decoration: underline;font-weight:bold;" class="category"><a href="http://open-news.net">Open-News</a></p></li>
</ul>
</td></tr>
<tr><td valign="top">
<ul><li style="margin: 0pt; padding: 0pt; background-color: white;"><p style="text-decoration: underline;font-weight:bold;" class="category">Generated at #{Time.new.strftime("%I:%M%p")}</p></li>
</ul>
</td></tr>
</table>
<div id="help"style="position:absolute;z-index:10;display:none;"><img src="help.gif" alt="help"><a href="#" onclick="document.getElementById('help').style.display = 'none'; return true;"><p <p style="text-decoration: underline;font-weight:bold;">&lt; quit help</p></a></div>
EOF


width = 600
height = 800
pencil_x = 130
pencil_y = 150
area = width * height
som_sqrt = 0
border = 5

f = Frame.new(pencil_x, pencil_y, width,height)

border = 10
#fileout.puts "<div id=\"cadre\" class=\"news\" style=\"border:#{border}px ridge #555;position:absolute;left:#{pencil_x-(1.5*border)}px;top:#{pencil_y-(1.5*border)}px;height:#{height+(border)}px;width:#{width+(border)}px;overflow:hidden;z-index:1;background-image:url(chains.gif);\"></div>"
np.articles.sort.reverse.each do
    |art|
    next if art.related_count == 0
    som_sqrt += Math.sqrt(art.related_count)
end#each
np.articles.sort.reverse.each do
    |art|
    next if art.related_count == 0
    f.add(Math.sqrt(art.related_count)*area / som_sqrt, art)
end#each_key

fileout.puts f.to_html(coltop)


fileout.puts "<div class=\"news\" style=\"text-align:center;position:absolute;left:130px;top:#{height+130}px;background-color: #abcdef;height:25px;width:#{width}px;overflow:hidden;font-size:10pt\"><p class=\"news\">&copy; 2005 - open-news.net. Tous droits reserv&eacute;s</p>"

fileout.puts "</body></html>"

end#def generate_html

generate_html(File.new('index.html','w+'), NewsLang::EN, 30)
generate_html(File.new('index_fr.html','w+'), NewsLang::FR, 10)
