// Aspell check functions
// Copyright 1999 by Kevin Atkinson under the terms of the LGPL

#ifndef as_check__
#define as_check__

#include "manager.hh"
#include "token.hh"
#include "filter.hh"
#include "itr_equal.hh"
#include "string_map.hh"
#include "exception.hh"
#include "file_exceps.hh"
#include "tl_string_map.hh"
#include "config.hh"

namespace afilter {
  struct MapReplReadError;
}

namespace aspell {

  using namespace autil;

  struct UnknownFilter : public Exception {
    string filter;
    UnknownFilter() {}
    UnknownFilter(const string & f);
    ~UnknownFilter() throw() {}
  };

  struct BadMapFile : public BadFileFormat {
    MapReplReadError * maperr;
    BadMapFile(const string & file, MapReplReadError * e)
      : BadFileFormat(file), maperr(e) {}
    ~BadMapFile() throw() {}
  };

  FilterItrPart * get_filter_itr_throw(const string & name, 
				       ConfigData &);

  template <typename Itr, typename EndF = itr_equal<Itr> >
  class CheckState {
  private:
    TokenItr itr;
    typedef FilterItrRootClass<Itr,EndF> RootClass;
    Manager * manager_;

    CheckState(const CheckState &);
    CheckState & operator = (const CheckState &);

    class Notifier : public ConfigData::Notifier {
      CheckState * cs;
    public:
      Notifier(CheckState * s) : cs(s) {}
      void item_added  (const PspellKeyInfo *ki, const char *) 
      {
	if (strcmp(ki->name, "filter") == 0)
	  cs->start_over();
      }
      void item_removed(const PspellKeyInfo *ki, const char *) 
      {
	if (strcmp(ki->name, "filter") == 0)
	  cs->start_over();
      }
      void all_removed(const PspellKeyInfo *ki, const char *) 
      {
	if (strcmp(ki->name, "filter") == 0)
	  cs->start_over();
      }

    };
    Notifier notifier_;

  public:
    typedef Itr  Iterator;
    typedef EndF EndFun;

    CheckState(Manager & s, const string & file_name = "") 
      : itr(s.lang()), manager_(&s), notifier_(this)
    {
      start_over(file_name);
      manager_->config().add_notifier(&notifier_);
    }
    ~CheckState() 
    {
      manager_->config().remove_notifier(&notifier_);
    }
    void start_over(const string & file_name = "");
    
    Manager & manager() {return *manager_;}
  
    const Itr & word_begin() const 
    {
      return static_cast<const RootClass *>
	(itr.word_begin().root())->true_itr();
    }
    const Itr & word_end()   const 
    {
      return static_cast<const RootClass *>
	(itr.word_end().root())->true_itr();
    }
    bool is_word() const {return itr.is_word();}
    const string & word() const {return itr.word();}
    bool at_end() const {return itr.at_end();}

    void advance() {itr.advance();}

    void add(FilterItrPart * i) {itr.add(i);}
    void add(const string & name) 
    {
      add(get_filter_itr_throw(name,manager_->config()));
    }

    void reset() {itr.reset();}
  
    void restart(const Itr & i)                 {itr.restart(RootClass(i));}
    void restart(const Itr & i, const EndF & e) {itr.restart(RootClass(i,e));}
    void restart(const Itr & i, const Itr & e)  {itr.restart(RootClass(i,e));}

    void backup() {itr.backup();}

    void scan(const Itr & stop) {itr.scan(RootClass(stop));}
  };

  template <typename Itr, typename EndF>
  void CheckState<Itr, EndF>::start_over(const string & file_name) {

    Config & options = manager_->config();
    
    unsigned int i = file_name.rfind('.');
    string extension;
    
    if (i != string::npos) {
      extension.assign(file_name, i+1,string::npos);
      for (i = 0; i != extension.size(); ++i) {
	extension[i] = tolower(extension[i]);
      }
    }
    if (extension == "tex") {
      if (!options.have("mode"))
	options.replace("mode", "tex");
    } else {
      ToLowerStringMap sgml_extns;
      options.retrieve_list("sgml-extension", sgml_extns);
      if (sgml_extns.have(extension.c_str()) && !options.have("mode")) {
	options.replace("mode", "sgml");
      }
    }

    itr.start_over();
    itr.add(new RootClass()); 

    StringMap filters;
    options.retrieve_list("filter", filters);
    StringMap::Emul els = filters.elements();
    while (!els.at_end())
      add(els.next().first);
  }

  struct DoNothing {
    void operator () () const {}
  };

  //FIXME: deal with conversion properly.
  //       ie. if the filter is already there don't convert
  template <class Itr, class EndF, class StatusF>
  void check (CheckState<Itr,EndF> & state,
	      const StatusF & print_status) 
  {
    while (!state.at_end() && 
	   (!state.is_word() || state.manager().check(state.word()))
	   )
      {
	if (state.is_word()) print_status();
	state.advance();
      }
  }

  template <class Itr, class EndF>
  inline void check (CheckState<Itr, EndF> & state) 
  {
    check(state, DoNothing());
  }
}
#endif
