require "rcon"
require "yaml"

class Server < ActiveRecord::Base
  include Extra

  DOMAIN_HLDS = 0
  DOMAIN_HLTV = 1
  DOMAIN_NS2 = 2
  HLTV_IDLE = 1200
  DEMOS = "/var/www/virtual/ensl.org/hlds_l/ns/demos"
  QSTAT = "/usr/bin/quakestat"
  TMPFILE = "tmp/server.txt"

  attr_accessor :rcon_handle, :pwd
  attr_protected :id, :user_id, :updated_at, :created_at, :map, :players, :maxplayers, :ping, :version

  validates_length_of [:name, :dns,], :in => 1..30
  validates_length_of [:rcon, :password, :irc], :maximum => 30, :allow_blank => true
  validates_length_of :description, :maximum => 255, :allow_blank => true
  validates_format_of :ip, :with => /\A[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\z/
    validates_format_of :port, :with => /\A[0-9]{1,5}\z/
    validates_format_of :reservation, :with => /\A[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]{1,5}\z/, :allow_nil => true
  validates_format_of :pwd, :with => /\A[A-Za-z0-9_\-]*\z/, :allow_nil => true

  scope :ordered, :order => "name"
  scope :hlds, :conditions => ["domain = ?", DOMAIN_HLDS]
  scope :ns2, :conditions => ["domain = ?", DOMAIN_NS2]
  scope :hltvs, :conditions => ["domain = ?", DOMAIN_HLTV]
  scope :active, :conditions => "active = 1"
  scope :with_players, :conditions => "players > 0"
  scope :reserved, :conditions => "reservation IS NOT NULL"
  scope :unreserved_now, :conditions => "reservation IS NULL"
  scope :unreserved_hltv_around,
    lambda { |time| {
    :select => "servers.*",
    :joins => "LEFT JOIN matches ON servers.id = matches.hltv_id
  AND match_time > '#{(time.ago(Match::MATCH_LENGTH).utc).strftime("%Y-%m-%d %H:%M:%S")}'
  AND match_time < '#{(time.ago(-Match::MATCH_LENGTH).utc).strftime("%Y-%m-%d %H:%M:%S")}'",
    :conditions => "matches.hltv_id IS NULL"} }
  scope :of_addr,
    lambda { |addr| {
    :conditions => {
      :ip => addr.match(/([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/)[0],
      :port => addr.match(/:([0-9]{1,5})/)[1] } } }
      scope :of_category,
        lambda { |category| {
        :conditions => {:category_id => category.id} }}

      has_many :logs
      has_many :matches
      has_many :challenges
      belongs_to :user
      belongs_to :recordable, :polymorphic => true

      before_create :set_category

      acts_as_versioned
      non_versioned_columns << 'name'
      non_versioned_columns << 'description'
      non_versioned_columns << 'dns'
      non_versioned_columns << 'ip'
      non_versioned_columns << 'port'
      non_versioned_columns << 'rcon'
      non_versioned_columns << 'password'
      non_versioned_columns << 'irc'
      non_versioned_columns << 'user_id'
      non_versioned_columns << 'official'
      non_versioned_columns << 'domain'
      non_versioned_columns << 'reservation'
      non_versioned_columns << 'recording'
      non_versioned_columns << 'idle'
      non_versioned_columns << 'default_id'
      non_versioned_columns << 'active'
      non_versioned_columns << 'recordable_type'
      non_versioned_columns << 'recordable_id'

      def domains
        {DOMAIN_HLTV => "HLTV", DOMAIN_HLDS => "NS Server", DOMAIN_NS2 => "NS2 Server"}
      end

      def to_s
        name
      end

      def addr
        ip + ":" + port.to_s
      end

      def players_s
        if players.nil? or max_players.nil?
          "N/A"
        else
          players.to_s + " / " + max_players.to_s
        end
      end

      def recording_s
        return nil if self.domain != DOMAIN_HLTV
        # recording.to_i > 0 ? Match.find(recording).to_s : recording
        recording
      end

      def reservation_s
        return nil if domain != DOMAIN_HLTV
        reservation
      end

      def graphfile
        File.join("public", "images", "servers", id.to_s + ".png")
      end

      def set_category
        self.category_id = (domain == DOMAIN_NS2 ? 45 : 44 )
      end

      def after_validation_on_update
        if reservation_changed?
          rcon_connect
          if reservation == nil
            rcon_exec "stop"
            self.recording = nil
            self.recordable = nil
            self.idle = nil
            save_demos if self.recording
            hltv_stop
          else
            if changes['reservation'][0].nil?
              hltv_start
              rcon_exec "stop"
              self.recording = recordable.demo_name if recordable and recordable_type == "Match" or recordable_type == "Gather"
              rcon_exec "record demos/" + self.recording if self.recording
            end
            rcon_exec "serverpassword " + pwd
            rcon_exec "connect " + reservation
            self.idle = DateTime.now
          end
          rcon_disconnect
        end
      end

      def execute command
        rcon_connect
        response = rcon_exec command
        rcon_disconnect
        response
      end

      def rcon_connect
        self.rcon_handle = RCon::Query::Original.new(ip, port, rcon)
      end

      def rcon_exec command
        response = rcon_handle.command(command)

        Log.transaction do
          Log.add(self, Log::DOMAIN_RCON_COMMAND, command)
          if response.to_s.length > 0
            Log.add(self, Log::DOMAIN_RCON_RESPONSE, response)
          end
        end

        response
      end

      def rcon_disconnect
        rcon_handle.disconnect
      end

      def hltv_start
        if nr = hltv_nr
          `screen -d -m -S "Hltv-#{nr[1]}" -c $HOME/.screenrc-hltv $HOME/hlds_l/hltv -ip 78.46.36.107 -port 28#{nr[1]}00 +exec ns/hltv#{nr[1]}.cfg`
            sleep 1
        end
      end

      def hltv_nr
        self.name.match(/Tv \#([0-9])/)
      end

      def hltv_stop
        if nr = hltv_nr
          sleep 5
          rcon_exec "exit"
          #`screen -S "Hltv-#{nr[1]}" -X 'quit'`
        end
      end

      def save_demos
        dir = case recordable_type
              when "Match" then
                recordable.contest.demos
              when "Gather" then
                Directory.find(Directory::DEMOS_GATHERS)
              end

        dir ||= Directory.find(Directory::DEMOS_DEFAULT)
        zip_path = File.join(dir.path, recording + ".zip")

        Zip::ZipOutputStream::open(zip_path) do |zos|
          if recordable_type == "Match"
            zos.put_next_entry "readme.txt"
            zos.write "Team1: " + recordable.contester1.to_s + "\r\n"
            zos.write "Team2: " + recordable.contester2.to_s + "\r\n"
            zos.write "Date: " + recordable.match_time.to_s + "\r\n"
            zos.write "Contest: " + recordable.contest.to_s + "\r\n"
            zos.write "Server: " + recordable.server.addr + "\r\n" if recordable.server
            zos.write "HLTV: " + addr + "\r\n"
            zos.write YAML::dump(recordable.attributes).to_s
          end
          Dir.glob("#{DEMOS}/*").each do |file|
            if File.file?(file) and file.match(/#{recording}.*\.dem/)
              zos.put_next_entry File.basename(file)
              zos.write(IO.read(file))
            end
          end
        end

        DataFile.transaction do
          unless dbfile = DataFile.find_by_path(zip_path)
            dbfile = DataFile.new
            dbfile.path = zip_path
            dbfile.directory = dir
            dbfile.save!
            DataFile.update_all({:name => File.basename(zip_path)}, {:id => dbfile.id})
          end
          if recordable_type == "Match"
            recordable.demo = dbfile
            recordable.save
          end
        end
      end

      def make_stats
        graph = Gruff::Line.new
        graph.title = name
        pings = []
        players = []
        labels = {}
        n = 0

        for version in versions.all(:order => "updated_at DESC", :limit => 30).reverse
          pings << version.ping.to_i
          players << version.players.to_i
          labels[n] = version.updated_at.strftime("%H:%M") if n % 3 == 0
          n = n + 1
        end

        graph.theme_37signals
        graph.data("Ping", pings, '#057fc0')
        graph.data("Players", players, '#ff0000')
        graph.labels = labels
        graph.write(graphfile)
      end

      def default_record
        #		if self.default
        #			rcon_exec "record demos/auto-" + Verification.uncrap(default.name)
        #			rcon_exec "serverpassword " + default.password
        #			rcon_exec "connect " + default.addr
        #		end
end

def is_free time
  challenges.around(time).pending.count == 0 and matches.around(time).count == 0
end

def can_create? cuser
  cuser
end

def can_update? cuser
  cuser and cuser.admin? or user == cuser
end

def can_destroy? cuser
  cuser and cuser.admin?
end

def self.refresh
  servers = ""
  Server.hlds.active.all.each do |server|
    servers << " -a2s " + server.ip + ":" + server.port.to_s
  end

  file = File.join(Rails.root, TMPFILE)
  system "#{QSTAT} -xml #{servers} | grep -v '<name>' > #{file}"

  doc = REXML::Document.new(File.new(file).read)
  doc.elements.each('qstat/server') do |server|
    hostname = server.elements['hostname'].text.split(':', 2)
    if s = Server.active.first(:conditions => {:ip => hostname[0], :port => hostname[1]})
      if server.elements.include? 'map'
        s.map = server.elements['map'].text
        s.players = server.elements['numplayers'].text.to_i
        s.max_players = server.elements['maxplayers'].text.to_i
        s.ping = server.elements['ping'].text
        s.map = server.elements['map'].text
        s.save
        s.make_stats
      end
    end
  end

  servers = ""
  Server.hltvs.reserved.each do |server|
    servers << " -a2s #{server.reservation}"
  end

  doc = REXML::Document.new(`#{QSTAT} -xml #{servers} | grep -v '<name>'`)
  doc.elements.each('qstat/server') do |server|
    hostname = server.elements['hostname'].text.split(':', 2)
    if s = Server.hltvs.reserved.first(:conditions => {:ip => hostname[0], :port => hostname[1]})
      if server.elements['numplayers'].text.to_i > 0
        s.update_attribute :idle, DateTime.now
      elsif (s.idle + HLTV_IDLE).past?
        s.reservation = nil
        s.save
      end
    end
  end
end

def self.move addr, newaddr, newpwd
  self.hltvs.all(:conditions => {:reservation => addr}).each do |hltv|
    hltv.reservation = newaddr
    hltv.pwd = newpwd
    hltv.save!
  end
end

def self.stop addr
  self.hltvs.all(:conditions => {:reservation => addr}).each do |hltv|
    hltv.reservation = nil
    hltv.save!
  end
end
end