require 'ftools'
require 'xhtmldiff'

# More information:
# * {EPF Wiki Data model}[link:files/doc/DATAMODEL.html]
# 
#--######################################################################
# Copyright (c) 2006 Logica
# 
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# 
# Onno van der Straaten:: initial implementation
#++######################################################################
# {Copyright (c) 2006 Logica}[link:files/COPYRIGHT.html]

class Version < ActiveRecord::Base
  
  belongs_to  :user
  belongs_to  :wiki, :foreign_key => 'wiki_id' # TODO not necessary?
  belongs_to  :page
  has_one     :checkout
  belongs_to  :source_version,        :class_name => 'Version', :foreign_key => 'version_id'
  belongs_to  :reviewer,              :class_name => 'User',    :foreign_key => 'reviewer_id'
  has_many    :child_versions,        :class_name => 'Version', :foreign_key => 'version_id'
  has_many    :comments

  # NOTE: it is not possible to use 'update' below, this is reserved by ActiveRecord. 
  # If you use it, the record won't save
  belongs_to  :baseline_update, :class_name => 'Update', :foreign_key => 'update_id'
  
  validates_presence_of :user, :wiki, :page
  
  BODY_PATTERN = /<body.*<\/body>/m
  LINKS_PATTERN = /<a.*?href=".*?".*?<\/a>/
  HREF_PATTERN = /href="(.*?)"/

  DIFF_STYLE =   ["<style type=\"text/css\">",
"  .diffdel,del.diffmod, .diffdel *, del.diffmod * {",
"  background-color:#FAA;",
"  text-decoration:line-through;",
"  }",
"  .diffins,ins.diffmod, .diffins *, ins.diffmod * {",
"  background-color:#AFA;",
"  text-decoration:underline;",
"  }",
"  .diffmod, .diffmod * {",
"  border: 2px solid #FE0;",
"}",
"</style>"].join("\n")
  
  def previous_version
    case self.version
    when 0 then nil 
    when nil then self.page.current_version 
    else Version.find(:first, :conditions => ['page_id=? and version=?', self.page_id, self.version - 1])
    end
  end
  
  # TODO this doesn't add value
  def baseversion
    return Version.find(:first, :conditions => ['wiki_id = ? and page_id=? and version=0',
    self.wiki_id, self.page_id])
  end  
  
  def current_version
    return self.page.current_version
  end
  
  # return the latest version of page in a site
  def self.find_latest_version(site, page)
    return Version.find(:first ,:order => 'version DESC', :conditions => ["page_id=? and wiki_id =?", page.id, site.id])    
  end  
  
  # return the latest version based on a version
  # TODO use page.last_version
  def latest_version
    return Version.find(:first ,:order => 'version DESC', :conditions => ["page_id=? and wiki_id =?", self.page, self.wiki])    
  end
  
  # Returns relative path of a version in folder 'public', 
  # the column rel_path stores the relatieve path in a Site folder
    def rel_path_root
    return self.path.gsub("#{ENV['EPFWIKI_ROOT_DIR']}#{ENV['EPFWIKI_PUBLIC_FOLDER']}/", '')
  end
  
  # use #tidy to tidy the file using HTML Tidy. 
  # This also removes any empty lines that may have been 
  # created by PinEdit.
  def tidy
    logger.info("Tidying file " + path)
    tidy_file(path)
  end
  
  
  # method #html read or writes the HTML of a version from or to the version file.
  # When writing HTML Tidy is used to cleanup the file
  def html
    IO.readlines(self.path).join
  end

  # Create the tmp diff source file, clean it using Tidy and prepare for use with XHTMLDiff
  def html4diff(h = nil)
    logger.info("Create tmp diff source file #{self.path_to_tmp_diff_html} using #{self.path}")    
    File.copy(self.path, self.path_to_tmp_diff_html)
    tidy_file(self.path_to_tmp_diff_html)    
    h = IO.readlines(self.path_to_tmp_diff_html).join if h.nil?
    h = BODY_PATTERN.match(h)[0]
    h = h.gsub(Page::TREEBROWSER_PLACEHOLDER, '').gsub(Page::TREEBROWSER_PATTERN, '')
    h = h.gsub(Page::SHIM_TAG_PATTERN, Page::SHIM_TAG) # TODO remove? This should not be necessary, see html
    h = h.gsub(/<!-- epfwiki.*end -->/m, '') # TODO remove?
    h = h.gsub('</body>','').gsub(/<body.*>/, '')
    
    # If the html originates from TinyMCE it contains extra tbody tags.
    # TinyMCE adds those and this is good XHTML but if
    # we strip those so we get better diff results
    # Because without it, compare with the baseversion (not modified by TinyMCE) will 
    # result false positives (changes)
    h = h.gsub(/<[\/]{0,1}(tbody){1}[.]*>/, '') 
    
    # TODO Workaround for Tidy adding title attributes which causes false changes
    #--
    # The question mark makes it lazy, so it won't match for instance  
    # title="Org"></a><span class="overview" it will match
    # title="Org" only
    #++
    h = h.gsub(/title="(.*?)"/, '') # WE REMOVE ALL TITLE ATTRIBUTES FOR BETTER DIFFS
    logger.debug("html4diff #{self.path}: ")
    #logger.debug(h)
    file = File.new(self.path_to_tmp_diff_html, 'w')
    file.puts(h)
    file.close    
    h 
  end

  # Create diff results using XHTMLDiff
  def xhtmldiff(from_version)
    content_from = "<div>\n" + from_version.html4diff + "\n</div>"
    content_to = "<div>\n" + self.html4diff + "\n</div>"

    diff_doc = REXML::Document.new
    diff_doc << (div = REXML::Element.new 'div')
    hd = XHTMLDiff.new(div)
    
    parsed_from_content = REXML::HashableElementDelegator.new(REXML::XPath.first(REXML::Document.new(content_from), '/div'))
    parsed_to_content = REXML::HashableElementDelegator.new(REXML::XPath.first(REXML::Document.new(content_to), '/div'))
    Diff::LCS.traverse_balanced(parsed_from_content, parsed_to_content , hd)
    diffs = ''
    diff_doc.write(diffs, -1, true, true)
    diffs    
  end
  
  # Create diff file using #xhtmldiff. The file is generated if doesn't exist of if one of the versions is checked out.
  def xhtmldiffpage(from_version, force = false)
    p = path_to_diff(from_version)
      if !File.exists?(p) || !from_version.checkout.nil? || !self.checkout.nil? || force
      logger.info("Generating xhtmldiffpage #{p}")
      diffs = xhtmldiff(from_version)
      h = page.html
      body_tag = Page::BODY_TAG_PATTERN.match(h)[0]
      h = h.gsub(BODY_PATTERN,body_tag + diffs + '</body>')
      h = h.gsub('</head>',DIFF_STYLE + '</head>')
      h = h.gsub(Page::PAGE_HEAD_SNIPPET_PATTERN, '')
      file = File.new(p, 'w')
      file.puts(h)
      file.close
    end
  end
  
  # Absolute path to the diff file and paths to the intermediate (tidied, cleaned) HTML files
  def path_to_diff(from_version)
    return "#{ENV['EPFWIKI_ROOT_DIR']}#{ENV['EPFWIKI_PUBLIC_FOLDER']}/#{relpath_to_diff(from_version)}"
  end
  
  # Relative path to the diff file
  def relpath_to_diff(from_version)
    return '/' + self.wiki.rel_path + '/' + self.page.rel_path + "_EPFWIKI_DIFF_V#{from_version.version}_V#{self.version}.html"  
  end
  
  # Path intermediate (tidied, cleaned) HTML files, prepared for XHTMLDiff
  def path_to_tmp_diff_html
    File.makedirs(ENV['EPFWIKI_DIFFS_PATH']) unless File.exists?(ENV['EPFWIKI_DIFFS_PATH'])    
    "#{ENV['EPFWIKI_DIFFS_PATH']}#{self.id.to_s}.html"  
  end
  
  # Compares hrefs to determine new or added 
  #--
  # Depends on covert_urls settings of TinyMCE
  #++
  # TODO implement
  #def diff_links(from_version)
  #  links = self.html.scan(LINKS_PATTERN)
  #  links_from = from_version.html.scan(LINKS_PATTERN)
  #  hrefs = links.collect {|link | HREF_PATTERN.match(link)[0]}
  #  hrefs_from = links_from.collect {|link | HREF_PATTERN.match(link)[0]}
  #  new_removed = [hrefs - hrefs_from, hrefs_from - hrefs]
  #  new_removed
  #end
  
  # TODO implement
  #def diff_img(from_version)
  #end

  #def diff_area_links
  #end
   
  def template?
    self.wiki.title == 'Templates' 
  end
  
  def base_version?
    !user_version?
  end
  
  def user_version?
    baseline_process_id.nil?
  end
  
  # #version_text returns version number, site title and baseline,
  # for example <tt>1 from OpenUP(OUP_20060721)</tt>
  def version_text
    if self.base_version? 
      return self.version.to_s + ' from ' + self.wiki.title + ' (' + self.baseline_process.title+ ')'
    else
      return self.version.to_s + ' from ' + self.wiki.title + ' (' + self.user.name + ')'
    end
  end
  
  def before_destroy
    logger.debug("Before Destroy: deleting file #{self.path}")
    File.delete(self.path)
  end
  
  # #uma_type_descr is used to retrieve the brief description for a template, see app/views/pages/new
  def uma_type_descr
    logger.debug("Returning uma_type_description from: #{self.inspect}")
    if self.note.blank? || self.note == 'Automatically created'
      match = /<table class="overviewTable".*?<td valign="top">(.*?)<\/td>/m.match(self.html)
      if match
        self.note = match[1]
        self.save!
        logger.info("Match found: #{self.note}")
      else
        logger.debug('No match')
      end
    end
    return self.note
  end
  
end
