require 'iconv'

# A Site can be a BaselineProcess (static, published website from EPF) or a Wiki, 
# which is an enhanced EPFC site. Baseline Processes are 
# used to create or update Wiki sites.
#
# Creation or update of a Wiki is a two step process for performance reasons.
# This way the second step can be performed using a job that runs at night.
# 
# More information:
# * {EPF Wiki Data model}[link:files/doc/DATAMODEL.html]

class Site < ActiveRecord::Base
  
  belongs_to :user
  has_many :pages
 
  validates_presence_of  :user_id, :title, :folder # TODO validate :user not :user_id
  
  # TODO Folde can contain . (dot) because it is created from a filename
  validates_format_of :folder, :message => 'should consist of letters, digits and underscores', :with =>  /([-_.\dA-Za-z])*/  
  
  # A wikifiable file is a HTML file
  HTML_FILE_PATTERN = /.*.htm(l)?/i
  
  # A wikifiable is not a Wiki file (a version file created using the Wiki)
  WIKI_FILE_PATTERN = /(.)*wiki(.)*/i
  
   def content_scanned?
    return !self.content_scanned_on.nil?
  end
  
  def baseline_processes_candidate
    returning bp_candidate = [] do
      if self.status == 'Ready' # TODO
        Site.find_baseline_processes.each do |bp|
          bp_candidate << bp unless self.baselines.include?(bp.baseline)
        end
      end    
    end
  end
  
  def wiki?
    return self.class.name == 'Wiki'
  end
  
  def baseline_process?
    return self.class.name == 'BaselineProcess'
  end
  
  def path 
    return "#{ENV['EPFWIKI_ROOT_DIR']}#{ENV['EPFWIKI_PUBLIC_FOLDER']}/#{ENV['EPFWIKI_WIKIS_FOLDER']}/#{self.folder}" if self.wiki?
    return "#{ENV['EPFWIKI_ROOT_DIR']}#{ENV['EPFWIKI_PUBLIC_FOLDER']}/#{ENV['EPFWIKI_SITES_FOLDER']}/#{self.folder}"
  end
  
  def rel_path
    return self.path.gsub(ENV['EPFWIKI_ROOT_DIR'] + "#{ENV['EPFWIKI_PUBLIC_FOLDER']}/",'')
  end
  
  def path2zip 
    return self.path + '.zip'
  end
  
  # Method #templates returns current versions of pages from the Wiki 'Templates'. 
  # These pages are templates for creating new pages. 
  def self.templates
    w = Wiki.find_by_title('Templates')
    raise "No Templates Wiki was found. There should always be a Wiki with title 'Templates' to provide templates for creating new pages" if !w
    w.pages.collect{|p| p.current_version if p.current_version}.compact # NOTE: a new checked out Template has no current version
  end
  
  def unzip_upload
    logger.info("Unzipping upload #{self.path2zip}")
    raise "Folder #{self.path} exists" if File.exists?(self.path)
    cmdline = "unzip -q \"#{self.path2zip}\" -d \"#{self.path}\""
    logger.debug("Executing command #{cmdline}")
    unzip_success =  system(cmdline)
    raise "Error executing command #{cmdline}: #{$?}" if !unzip_success
  end
  
  # array of HTML files that are candate Wikification
  def self.files_html(path)
    paths = Array.new
     (Dir.entries(path) - [".", ".."]).each do |entry| 
      new_path = File.expand_path(entry, path)
      if FileTest.directory?(new_path)   
        paths = paths + Site.files_html(new_path)
      else
        paths << new_path if !HTML_FILE_PATTERN.match(entry).nil? && WIKI_FILE_PATTERN.match(entry).nil?
      end
    end
    return paths
  end
  
  # Find wikifiable files
  def files_wikifiable
    raise 'Path can\'t be blank' if self.path.blank?
    logger.info("Finding wikifiable files in #{self.path}")
    
    # TODO workaround for ArgumentError: invalid byte sequence in UTF-8
    # http://po-ru.com/diary/fixing-invalid-utf-8-in-ruby-revisited/
    # ic = Iconv.new('UTF-8//IGNORE', 'UTF-8') # 
    # valid_string = ic.iconv(untrusted_string + ' ')[0..-2]
    
    ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
    paths = []
    Site.files_html(self.path).each do |p|
      #logger.debug(p)
      c = File.read(p)
      c_trusted = ic.iconv(c +  ' ')[0..-2]
      match = Page::ELEMENT_TYPE_PATTERN.match(c_trusted)
      if !match.nil?
        paths << p 
      end
    end
    logger.debug("Found #{paths.size} wikifiable files\n" + (paths[0..14]+["...(skipping remaining #{paths.size}) files"]).join("\n"))
    paths
  end
  
  def self.update
    raise "Raise for testing exception handling in rake update" if ENV['EPFWIKI_RAISE_IN_RAKE_UPDATE'] == 'Y' # for test purposes
    cadmin = User.find_central_admin
    BaselineProcess.find_2scan.each do |bp|
      bp.scan4content
    end # end scan
    expired_by_update = false
    Update.find_todo.each do |update|
      update.do_update
      expired_by_update = true
    end # end update
    Wiki.expire_all_pages unless expired_by_update
  rescue => e
    Rails.logger.info("Error updating sites: " + e.message + "\n" + e.backtrace.join("\n"))
    Notifier.email(User.find_central_admin, 'Error running job_daily', [], e.message + "\n" + e.backtrace.join("\n")).deliver
  end
  
  def self.reports(runtime = Time.now)
    reps_sent = []
    raise "Raise for testing exception handling in rake update" if ENV['EPFWIKI_RAISE_IN_RAKE_UPDATE'] == 'Y' # for test purposes
    (Wiki.find(:all, :conditions => ['obsolete_on is null']) << nil).each do |w| # Wiki.new for notification for all sites
      Rails.logger.info("Site Reports for site " + w.title) if w
      Rails.logger.info("Global Reports") unless w
      reps = [Report.new('D', w, runtime)] # daily 
      reps << Report.new('W', w, runtime) if runtime.wday == 1 # monday, sunday is 0
      reps << Report.new('M', w, runtime) if runtime.day == 1 # first day of the month 
      reps.each do |r|
        Rails.logger.info("Report_type: " + r.report_type + "\nr.items.empty?: " + r.items.empty?.to_s + "\nr.users.empty? " + r.users.empty?.to_s )
        Notifier.summary(r).deliver unless r.items.empty? or r.users.empty? # only deliver when content and users
        reps_sent << r unless  r.items.empty? or r.users.empty?
      end
    end
    reps_sent
  rescue => e
    Rails.logger.info("Error running job_daily: " + e.message + "\n" + e.backtrace.join("\n"))
    Notifier.email(User.find_central_admin, 'Error running job_daily', [], e.message + "\n" + e.backtrace.join("\n")).deliver
  end
  
  def self.changed_items(rep)
    cond = ['created_on > ? and created_on < ?', rep.starttime, rep.endtime]
    if rep.site
      site_cond = ['created_on > ? and created_on < ? and site_id = ?', rep.starttime, rep.endtime, rep.site]
      site_cond2 = ['created_on > ? and created_on < ? and wiki_id = ?', rep.starttime, rep.endtime, rep.site]
      site_cond3 = ['created_on > ? and created_on < ? and tool=? and site_id = ?', rep.starttime, rep.endtime, 'Wiki', rep.site]
    else
      site_cond = site_cond2 = cond
      site_cond3 = ['created_on > ? and created_on < ? and tool=?', rep.starttime, rep.endtime, 'Wiki']
    end
    items = UserVersion.find(:all, :conditions => site_cond2) +
    Comment.find(:all, :conditions => site_cond) +
    Upload.find(:all, :conditions => cond) +
    Wiki.find(:all, :conditions => cond)+
    Update.find(:all, :conditions => site_cond2) +
    User.find(:all, :conditions => cond) +
    WikiPage.find(:all, :conditions => site_cond3)
    Checkout.find(:all)
    items.sort_by {|item|item.created_on}
  end
  
  ###########
  # private # TODO after upgrade we need this
  ###########
  
 
  # action #copy_to copies the content of a site to another site (theDestSite). Files are overwritten if they exist in the destination site.
  # NOTE: Ruby does not have a copy + overwrite command?
  def copy_to(theDestSite, theFolderPath = nil)
    if  theFolderPath
     (Dir.entries(theFolderPath) - [".", ".."]).each do |aEntry|
        aPath = File.expand_path(aEntry, theFolderPath)
        aDestPath = aPath.gsub(self.path, theDestSite.path)
        if  FileTest.directory?(aPath)
          logger.info("Copying folder " + aPath + " to " + aDestPath)
          FileUtils.makedirs(aDestPath)
          copy_to(theDestSite, aPath)
        else
          if  !FileTest.exists?(aDestPath)
            logger.info("New file copied " + aPath + " to " + aDestPath)
            FileUtils.copy(aPath, aDestPath)
          else
            if  FileUtils.cmp(aPath, aDestPath)
              logger.info("Not copied because equal: " + aPath)
            else
              logger.info("Overwritten: " + aPath)
              FileUtils.remove_dir(aDestPath) # TODO changed by upgrade was File.delete
              FileUtils.copy(aPath, aDestPath)
            end
          end
        end
      end 
    else
      logger.info("Copying content from site " + self.title + " to " + theDestSite.title)
      logger.info("Source folder: " + self.path + ". Destination folder: " + theDestSite.path)
      copy_to(theDestSite, self.path)
    end
  end
  
end
