# == Schema Information
# Schema version: 51
#
# Table name: token
#
#  token_id                     :integer         primary key
#  kolejnosc                    :integer         
#  akapit_id                    :integer         not null
#  xpointer                     :text            
#  segmentation_xmlid           :text            
#  morphosyntactic_xmlid        :text            
#  fs_morph_comment             :text            
#  path_id                      :integer         not null
#  orth                         :text            
#  czy_interp                   :boolean         not null
#  ns_poprzedza                 :boolean         
#  ns_nastepuje                 :boolean         
#  czy_konczy_zdanie            :boolean         
#  czy_konczy_zdanie_updated_at :timestamp       
#  sg_choice_id                 :integer         
#  sg_variant_id                :integer         
#  dodany                       :boolean         not null
#  chosen                       :boolean         default(TRUE)
#  chosen_updated_at            :timestamp       
#  superancja                   :text            
#  created_at                   :timestamp       
#  updated_at                   :timestamp       
#

 #
 # This file is part of the Anotatornia suite.
 # 
 # Copyright © 2007, 2008, 2009, 2010 by Instytut Podstaw Informatyki
 # Polskiej Akademii Nauk (IPI PAN; Institute of Computer Science, Polish
 # Academy of Sciences; cf. www.ipipan.waw.pl).  All rights reserved.
 # 
 # This file may be distributed and/or modified under the terms of the
 # GNU General Public License version 3 as published by the Free Software
 # Foundation and appearing in the file COPYING included in the packaging
 # of this file.  (See http://www.gnu.org/licenses/translations.html for
 # unofficial translations.)
 # 
 # A commercial license is available from IPI PAN (contact
 # Michal.Ciesiolka.waw.pl or ipi.waw.pl for more
 # information).  Licensees holding a valid commercial license from IPI
 # PAN may use this file in accordance with that license.
 # 
 # This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
 # THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 # PURPOSE.
 #

require 'jcode'

class Token < ActiveRecord::Base
  has_many :interpretacja, :order => "numer_lex_token, interpretacja_id", 
  :include => :leksem,
  :conditions => [ " leksem.leksem_id not in ( " + Leksem.idi_nulli_et_ignoti.join(', ') +")" ]
  # wołamy #id dla #{Leksem.nullus}, żeby zrobił się wyjątek, jeśli nullus nie istnieje: przekazanie nila na prawą stronę różności spowodowałoby zapewnienie, że zapytanie zwróci 0 rekordów.

  has_many :interpretacja_export # widok do generowania wypluwki (migr. 051)

  has_many :interpretacja_wo_leksem, :class_name => "Interpretacja",
  :order => "numer_lex_token, interpretacja_id"
  # bo #{token.interpretacja.maximum( :nr_lex_token )} nie zadziałało: nie został dołączony leksem (migracja 038).

  
  has_many :interpretacja_anot, :order => "akapit_transzy_id"
  has_many :sens_anot
  belongs_to :akapit
  belongs_to :path

  belongs_to :sg_variant
  belongs_to :sg_choice

  has_one :the_disambiguation, :class_name => "Interpretacja", 
  :conditions => {:disamb => true}, :include => :leksem

  has_many :koniec_zdania_anot
  
  serialize :superancja

  def disambs( param )
    # zwraca tablicę dyzambiguacyj (tablicę obiektów klasy Interpretacja).
    # #param może być idem użytkownika (gdy #{Integer}) albo idem akapitu transzy (gdy hasz). 
    # Najpierw patrzymy, czy token jest zdezambiguowany — co możliwe tylko gdy wybór pewnej interpretacji został uzgodniony przez obie anotatorki. Jeśli token nie został zdezambiguowany, to
    #  szukamy interpretacji wyróżnionej przez daną anotatorkę (przez dany akapit transzy).
##    @@poprz ||= Time.now
##    @@nast ||= Time.now
##    loguj = Proc.new { |w| 
##      @@poprz, @@nast = @@nast, Time.now
##      logger.info "@@@@ tok.#{self.id}.disambs w.#{w}: #{@@nast - @@poprz}"
##    }
    
    #    loguj.call( 65 )
    if the_disa = self.the_disambiguation
      return [ the_disa ]
      ##                                           loguj.call( 66 )
    else
      ##                                           loguj.call( 68 )
      return ( if param.kind_of?( Integer )
          phash = { :uzytkownik_id => param }
        elsif param.kind_of?( Hash )
          # wariant z haszem dołożony 2008/09/01, żeby wyszukiwać po numerze akapitu transzy.
          phash = param.dup
        else
         phash = nil
        end
        
        phash[ :token_id ] = self.token_id if phash
        
        if phash
          ##                                                  loguj.call( :przed_ia_find )
          ##                    logger.info "@@@@ #{phash.inspect}"
          ia=InterpretacjaAnot.find(
                                    :first,
                                    :conditions => phash )
          ##                                                   loguj.call( :po_ia_find )
        else
          ia = nil
        end
        
        if ia :   [ ia.interpretacja ]
        else  []
        end )
    end

  end # of #{disambs}.

  def zambiguuj
    logger.info "@@@@ ambiguujemy token #{self.id} »#{self.orth}«"
    di = self.the_disambiguation
    if di
      di.disamb = false
      di.save!
    end
  end

  
  def do_sensu?( param )
    # To musi być zrelatywizowane do anotatora, 
    # bo inny anotator mógł wybrać inną dyzambiguację,
    # która nie ma leksemu osensowanego.
    # Jeśli do sensu, to zwracamy id dyzambiguacji
    ## logger.info( "do_sensu, param=::::#{param}::::" )
    disas = self.disambs( param ) # na tym etapie anotacji jest zdezambiguowany.
    if disas.size == 1  
      if disas[0].cz_m_leksem
        # część mowy jest nie dla wszystkich określona, dlatego najpierw sprawdzamy, czy nie nil
        return disas[0].id
      else return false
      end
      else
      return false
    end
  end # of #{do_sensu?( param )}


  def sensy( param )
    # zwraca tablicę sensów: #[(xmlid + short_def), sensy_id]
    # dla sensów dotąd nie przypisanych zwraca [ … , … , -sensy_id ]
    le187 = self.disambs( param )[0].cz_m_leksem # na tym poziomie anotacji jest zdezambiguowany
    le187.sensy # one są posortowane po #sensy_id — patrz \file{cz_m_leksem.rb}.
    end # of #{sensy( param )}.


  def interp?( param )
    it = ( self.disambs( param )[ 0 ] || self.interpretacja[0] )
    if it
      it.leksem.klasa_gram.klasa_gram_ozn == KlasaGram::INTERP
    else nil
    end
  end

  

  def the_sens( param = {} )
    # jeśli sensu nie ma lub jest więcej niż 1 --- zwraca nil.
    # wpp. zwraca obiekt klasy SensyLeksemu.
    # najpierw sprawdza, czy jest sens zaanotowany przez tego uza
    # a potem z sensów leksemów.
    sa187 = SensAnot.znajdz( self.id, param )
    
    if sa187 : return sa187.sensy
    else 
      sls187 = self.disambs( param )[0]. # na tym poziomie anotacji jest zdezambiguowany.
        cz_m_leksem.sensy
      if sls187.size == 1
        return sls187[0]
      else return nil
      end
    end
  end # of #{the_sens( param )}.
  

  def xml_id
    if self.original_id : return self.original_id
    else return self.token_id.to_s 
    end
  end


  def <=>( drugi )
    # z założenia będziemy porównywać tylko tokeny tego samego akapitu.
    # porównania potrzebujemy do posortowania listy potencjalnych głów frazy
    if drugi.respond_to?(:nr_wyrazu_zd)
      drid = drugi.nr_wyrazu_zd.to_i
    else drid = drugi.to_s.to_i
    end
    return( self.nr_wyrazu_zd.to_i <=> drid )
  end


  def lemat( param )
    di = ( self.disambs( param )[0] || self.interpretacja[0] )
    di.leksem.lemat if di
  end



  def najbliższy( minmax, inequal, ponad_akapity=nil )
    # 2010/2/24 w pełni dualna do #{poprzedni}.

    najbl = nil

    unless self.chosen? # we're not chosen. That means we are in a segmentation variant (our sg_variant_id is not null). So first we try return previous token from our variant and if there's no such then from other chosen tokens.
      najbl = Token.find( :first, :conditions =>
                          [ " kolejnosc = ( select #{minmax}( kolejnosc ) from token " +
                            " where kolejnosc#{inequal}#{self.kolejnosc} and " +
                            " akapit_id=#{self.akapit_id} and " +
                            " sg_variant_id=? )", self.sg_variant_id ]
                          )
    end

    unless najbl
      najbl  = Token.find( :first, :conditions =>
                           [ " kolejnosc=(select #{minmax}(kolejnosc) from token " +
                             " where kolejnosc#{inequal}#{self.kolejnosc} and " +
                             " ( sg_choice_id<>? or sg_choice_id is null ) and " +
                             " akapit_id=#{self.akapit_id} and chosen='t' )", self.sg_choice_id ]
                           )
    end 

    if ponad_akapity and not najbl
      # potrzebujemy tego do poprawienia xpointerów. Wówczas patrzymy na xpointery poprzedniego i następnego tokenu oryginalnego, czyli #{dodany=false}.
      najbl =  Token.find( :first, :conditions =>
                           " kolejnosc=(select #{minmax}(kolejnosc) from token " +
                           " where kolejnosc#{inequal}#{self.kolejnosc} and " +
                           " path_id=#{self.path_id} and dodany='f' )" # poprzedniość/następność nie ma sensu między różnymi ścieżkami Korpusu.
                           )
    end

    najbl
  end # of #{najbliższy}

  
  def poprzedni( ponad_akapity=nil )
    self.najbliższy( "max", "<", ponad_akapity )
  end

  
  def następny( ponad_akapity=nil )
    self.najbliższy( "min", ">", ponad_akapity )
  end
  

  alias :nastepny :następny


  def odsmiec_interpretacje
    Interpretacja.odsmiec( :token_id => self.id )
  end# of #{odsmiec_interpretacje}.


  def sg_variant_anot( akathasz )
    if sgvid = self.sg_variant_id
      SgVariantAnot.find( :first, :conditions =>
                          akathasz.dup.update( :sg_variant_id => sgvid ))
    else nil
    end
  end # of #{sg_variant_anot}.


  def segmentny?( akathasz )
    self.chosen and (
                     ( not ( sgvan = self.sg_variant_anot( akathasz ) )) or
                     sgvan.chosen? )
  end# of #{segmentny?}

  
  def self.update_ns_nastepuje( ## akapit_od, akapit_do, 
                               force=nil )
    if self.find_by_sql(
                        "select 1 as val from token where dodany='f' and " +
                        " ns_nastepuje is null limit 1"  )[0]
      coś_jest_na_rzeczy = true
    else
      coś_jest_na_rzeczy = false
    end
    
    if  force ##or not self.find(:first, :conditions => {
      ## :ns_nastepuje => true, :akapit_id => akapit_od..akapit_do } )
      zawężenie = ""
    else
      zawężenie = " and ns_nastepuje is null "
    end

    # 2009/10/8 nie jest dla mnie jasne, czy poniższe \ac{SQL}\dy e to nie jest stąpanie po kruchym lodzie: a~co jeśli nps przed wariantem segmentacyjnym? 
    self.connection.execute " update token set  ns_nastepuje='t' " +
      " where token_id in " +
      " ( select token_id-1 from token t1 where t1.ns_poprzedza='t' ) " +
      " and dodany='f' " + zawężenie
      ##  " and akapit_id between #{akapit_od} and #{akapit_do} "
    # jeszcze kwestia źle przypisanych nps\dy ów:
    self.connection.execute " update token set  ns_nastepuje='f' " +
      " where token_id in " +
      " ( select token_id-1 from token t1 where t1.ns_poprzedza='f' ) " +
      " and dodany='f' " + zawężenie
      ##      " and akapit_id between #{akapit_od} and #{akapit_do} "
    if zły_t = self.beginning_ns_poprzedza[0]
      ## raise "detected token with 'nps' attribute beginning a choice: #{zły_t.id} »#{zły_t.orth}«"
    end
    return ( coś_jest_na_rzeczy or force )
  end # of #{self.update_ns_nastepuje}


  def self.beginning_ns_poprzedza
    self.find( :all, :conditions => "ns_poprzedza='t' and token_id in (select min(token_id) from token t1 where sg_variant_id is not null group by sg_variant_id)")
  end # of #{self.beginning_ns_poprzedza}
  

  ##  update_ns_nastepuje  


  def lex_msd
    # Warning: this method uses the #{interpretacja_export} view so skips the interpretations that are manually added and are not disambiguations.
    #    zwraca tablicę tablic interpretacyj pogrupowanych podług leksemów.
    intex = self.interpretacja_export

    lexids_u = intex.collect { |int199| int199.leksem_id }.uniq # są w~odp. kolejności
     
    igr199 = intex.group_by { |int199| int199.leksem_id }
    # tu  interpretacje exportowe grupujemy podług #{leksem_id}.

    lexes = lexids_u.collect { |lid199| 
      igr199[ lid199 ]
    } # a tu zamieniamy hasz na tablicę.

    return lexes
  end # of #{lex_msd}


  def self.uniq_superancje!
    self.find( :all, :conditions => "superancja is not null" ).each { |tok|
      tok.superancja.uniq!
      tok.save!
    }

    true

  end # of self.uniq_superancje!

  
  def superancja_include?( poziom )
    (ssu = self.superancja)  and ssu.include?( poziom )
  end

  def self.update_kolejnosc
    if self.find_by_sql( "select * from token where kolejnosc is null limit 1" )[0]
      self.connection.execute "update token set kolejnosc = token_id*216 where kolejnosc is null"
    end
  end

  update_kolejnosc

  def odżółć( poziom )
    if self.superancja
      self.superancja -= [ poziom ].flatten
      self.superancja = nil  unless self.superancja[0]
      self.save!
    end
  end # of #{odżółć}


  def choice_card
    self.class.find_by_sql( 
                           [ "select count(*) as card from sg_variant where sg_choice_id=?",
                             self.sg_choice_id ] )[0].card.to_i
  end

  
  def sg_possies
    # możliwości dla „wybierz/odrzuć ten wariant segmentacyjny”: jeśli jesteśmy w~co najmniej trójce wariantów, to nie możemy odrzucać, bo by to nie było jednokrokowe.
    if self.choice_card <= 2 
      [ true, false ]
    else [ true ]
    end

  end
  

  def self.gsub_SHY
    sth_done211 = false
    akids211 = []
    self.find( :all, :conditions => {:orth => '­'}).each { |t211|
      t211.orth = t211.orth.gsub('­', '-')
      t211.save!
      akids211 << t211.akapit_id
    }
    
    puts akids211.inspect

    Akapit.find( akids211.uniq ).each{ |aka211|
      aka211.update_treści
      sth_done211 = true unless sth_done211
    }
    
    return sth_done211
  end # of #{self.gsub_SHY}


  def self.sprawdź_xpointer( id )
    
    t = self.find( id )
    if t.sg_variant_id 
      tt = t.sg_variant.token
      tt = [ tt[0].poprzedni(:ponad_akapity) , tt, tt[-1].następny( :ponad_akapity )  ].flatten
    else
      tt = [t.poprzedni( :ponad_akapity ), t, t.następny( :ponad_akapity ) ]
    end

    tt.each { |t1|
      if t1
        puts "#{t1.id}  #{if t1.ns_poprzedza? : '<ns>' else '<spacja>' end} " +
          " #{t1.orth} #{if t1.ns_nastepuje : '<ns>' else '<spacja>' end} #{t1.xpointer}" +
          " (sgv #{t1.sg_variant_id.inspect})"
      else
        puts "<nil>" 
      end
    }

    nil
  end



  def self.ustaw_xpointer_na( tokid, xpo )
    t1 =     self.find( :first, :conditions => "token_id=#{tokid}" )
    if   t1 and  t1.xpointer != xpo
      t1.xpointer = xpo
      t1.save!
    end
  end # of #{self.popraw_xpointer}

  
  def self.popraw_xpointery_nullowe
    sth_done = false
    self.find( :all, :conditions => "xpointer is null" ).each { |t|
    
      sth_done = true unless sth_done
      
      tpo = t.poprzedni( :ponad_akapitami )
     
      if tpo 
        md = /^(.*),(\d+),(\d+)\)$/.match( tpo.xpointer )
      
        md_poprz = [ md[ 1 ] + ',', # dodajemy przecinek, bo xpointery mają postać „…ab,nr,nr”.
                     md[ 2 ].to_i, md[3].to_i ]
      
        t.xpointer = md_poprz[0] + "#{md_poprz[1]+md_poprz[2]+if t.ns_poprzedza? : 0 else 1 end},#{t.orth.jlength}" + ')'

      else # the previous token does not exist. Then the next one must exist since paragraphs are about 50-word.
        md = /^(.*),(\d+),(\d+)\)$/.match( t.następny( :ponad_akapitami ).xpointer )
        md_nast = [ md[ 1 ] + ',', # dodajemy przecinek, bo xpointery mają postać „…ab,nr,nr”.
                    md[ 2 ].to_i, md[3].to_i ]

        t.xpointer = md_nast[0] + "#{md_nast[1]-(t.orth.jlength+if t.ns_nastepuje? : 0 else 1 end)},#{t.orth.jlength}" + ')'

      end

      t.save!
      
      puts "poprz: #{if t.poprzedni : t.poprzedni.xpointer end}        id: #{t.id} #{t.ns_poprzedza.inspect} #{t.orth} #{t.xpointer}      nast: #{if t.następny : t.następny.xpointer end}"
      
    }

    sth_done
    
  end # of #{self.popraw_xpointery_nullowe}



end # of class.


### Local Variables: 
### mode: ruby
### End:
