/* Boost.MultiIndex example of composite keys.
 *
 * Copyright 2003-2005 Joaquín M López Muñoz.
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *
 * See http://www.boost.org/libs/multi_index for library home page.
 */

#if !defined(NDEBUG)
#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING
#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE
#endif

#include <boost/call_traits.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/tokenizer.hpp>
#include <functional>
#include <iostream>
#include <iterator>
#include <map>
#include <string>

using namespace boost::multi_index;

/* A file record maintains some info on name and size as well
 * as a pointer to the directory it belongs (null meaning the root
 * directory.)
 */

struct file_entry
{
  file_entry(
    std::string name_,unsigned size_,bool is_dir_,const file_entry* dir_):
    name(name_),size(size_),is_dir(is_dir_),dir(dir_)
  {}

  std::string       name;
  unsigned          size;
  bool              is_dir;
  const file_entry* dir;

  friend std::ostream& operator<<(std::ostream& os,const file_entry& f)
  {
    os<<f.name<<"\t"<<f.size;
    if(f.is_dir)os<<"\t <dir>";
    return os;
  }
};

/* A file system is just a multi_index_container of entries with indices on
 * file and size. These indices are firstly ordered by directory, as commands
 * work on a current directory basis. Composite keys are just fine to model
 * this.
 * NB: The use of derivation here instead of simple typedef is explained in
 * Compiler specifics: type hiding.
 */

struct name_key:composite_key<
  file_entry,
  BOOST_MULTI_INDEX_MEMBER(file_entry,const file_entry*,dir),
  BOOST_MULTI_INDEX_MEMBER(file_entry,std::string,name)
>{};

struct size_key:composite_key<
  file_entry,
  BOOST_MULTI_INDEX_MEMBER(file_entry,const file_entry* const,dir),
  BOOST_MULTI_INDEX_MEMBER(file_entry,unsigned,size)
>{};

/* see Compiler specifics: composite_key in compilers without partial
 * template specialization, for info on composite_key_result_less
 */

typedef multi_index_container<
  file_entry,
  indexed_by<
    /* primary index sorted by name (inside the same directory) */
    ordered_unique<
      name_key
#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)
      ,composite_key_result_less<name_key::result_type>
#endif
    >,
    /* secondary index sorted by size (inside the same directory) */
    ordered_non_unique<
      size_key
#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)
      ,composite_key_result_less<size_key::result_type>
#endif
    >
  >
> file_system;

/* typedef's of the two indices of file_system */

typedef nth_index<file_system,0>::type file_system_by_name;
typedef nth_index<file_system,1>::type file_system_by_size;

/* We build a rudimentary file system simulation out of some global
 * info and a map of commands provided to the user.
 */

static file_system fs;                 /* the one and only file system */
static file_system_by_name& fs_by_name=fs;         /* name index to fs */
static file_system_by_size& fs_by_size=get<1>(fs); /* size index to fs */
static const file_entry* current_dir=0;            /* root directory   */

/* command framework */

/* A command provides an execute memfun fed with the corresponding params
 * (first param is stripped off as it serves to identify the command
 * currently being used.)
 */

typedef boost::tokenizer<boost::char_separator<char> > command_tokenizer;

class command
{
public:
  virtual ~command(){}
  virtual void execute(
    command_tokenizer::iterator tok1,command_tokenizer::iterator tok2)=0;
};

/* available commands */

/* cd: syntax cd [.|..|<directory>] */

class command_cd:public command
{
public:
  virtual void execute(
    command_tokenizer::iterator tok1,command_tokenizer::iterator tok2)
  {
    if(tok1==tok2)return;
    std::string dir=*tok1++;

    if(dir==".")return;
    if(dir==".."){
      if(current_dir)current_dir=current_dir->dir;
      return;
    }

    file_system_by_name::iterator it=fs.find(
      boost::make_tuple(current_dir,dir));
    if(it==fs.end()){
      std::cout<<"non-existent directory"<<std::endl;
      return;
    }
    if(!it->is_dir){
      std::cout<<dir<<" is not a directory"<<std::endl;
      return;
    }
    current_dir=&*it;
  }
};
static command_cd cd;

/* ls: syntax ls [-s] */

class command_ls:public command
{
public:
  virtual void execute(
    command_tokenizer::iterator tok1,command_tokenizer::iterator tok2)
  {
    std::string option;
    if(tok1!=tok2)option=*tok1++;

    if(!option.empty()){
      if(option!="-s"){
        std::cout<<"incorrect parameter"<<std::endl;
        return;
      }

      /* list by size */

      file_system_by_size::iterator it0,it1;
      boost::tie(it0,it1)=fs_by_size.equal_range(
        boost::make_tuple(current_dir));
      std::copy(it0,it1,std::ostream_iterator<file_entry>(std::cout,"\n"));

      return;
    }

    /* list by name */

    file_system_by_name::iterator it0,it1;
    boost::tie(it0,it1)=fs.equal_range(boost::make_tuple(current_dir));
    std::copy(it0,it1,std::ostream_iterator<file_entry>(std::cout,"\n"));
  }
};
static command_ls ls;

/* mkdir: syntax mkdir <directory> */

class command_mkdir:public command
{
public:
  virtual void execute(
    command_tokenizer::iterator tok1,command_tokenizer::iterator tok2)
  {
    std::string dir;
    if(tok1!=tok2)dir=*tok1++;

    if(dir.empty()){
      std::cout<<"missing parameter"<<std::endl;
      return;
    }

    if(dir=="."||dir==".."){
      std::cout<<"incorrect parameter"<<std::endl;
      return;
    }

    if(!fs.insert(file_entry(dir,0,true,current_dir)).second){
      std::cout<<"directory already exists"<<std::endl;
      return;
    }
  }
};
static command_mkdir mkdir;

/* table of commands, a map from command names to class command pointers */

typedef std::map<std::string,command*> command_table;
static command_table cmt;

int main()
{
  /* fill the file system with some data */

  file_system::iterator it0,it1;
  
  fs.insert(file_entry("usr.cfg",240,false,0));
  fs.insert(file_entry("memo.txt",2430,false,0));
  it0=fs.insert(file_entry("dev",0,true,0)).first;
    fs.insert(file_entry("tty0",128,false,&*it0));
    fs.insert(file_entry("tty1",128,false,&*it0));
  it0=fs.insert(file_entry("usr",0,true,0)).first;
    it1=fs.insert(file_entry("bin",0,true,&*it0)).first;
      fs.insert(file_entry("bjam",172032,false,&*it1));
  it0=fs.insert(file_entry("home",0,true,0)).first;
    it1=fs.insert(file_entry("andy",0,true,&*it0)).first;
      fs.insert(file_entry("logo.jpg",5345,false,&*it1)).first;
      fs.insert(file_entry("foo.cpp",890,false,&*it1)).first;
      fs.insert(file_entry("foo.hpp",93,false,&*it1)).first;
      fs.insert(file_entry("foo.html",750,false,&*it1)).first;
      fs.insert(file_entry("a.obj",12302,false,&*it1)).first;
      fs.insert(file_entry(".bash_history",8780,false,&*it1)).first;
    it1=fs.insert(file_entry("rachel",0,true,&*it0)).first;
      fs.insert(file_entry("test.py",650,false,&*it1)).first;
      fs.insert(file_entry("todo.txt",241,false,&*it1)).first;
      fs.insert(file_entry(".bash_history",9510,false,&*it1)).first;

  /* fill the command table */

  cmt["cd"]   =&cd;
  cmt["ls"]   =&ls;
  cmt["mkdir"]=&mkdir;

  /* main looop */

  for(;;){
    /* print out the current directory and the prompt symbol */

    if(current_dir)std::cout<<current_dir->name;
    std::cout<<">";

    /* get an input line from the user: if empty, exit the program */

    std::string com;
    std::getline(std::cin,com);
    command_tokenizer tok(com,boost::char_separator<char>(" \t\n"));
    if(tok.begin()==tok.end())break; /* null command, exit */

    /* select the corresponding command and execute it */

    command_table::iterator it=cmt.find(*tok.begin());
    if(it==cmt.end()){
      std::cout<<"invalid command"<<std::endl;
      continue;
    }

    it->second->execute(++tok.begin(),tok.end());
  }
  
  return 0;
}
