/*
 *  mmatrix.h
 *  (C) Porkolab Zoltan, ELTE, Budapest, Hungary
 *  (C) 2003
 */

#ifndef MMATRIX_H
#define MMATRIX_H

#include <algorithm>
#include <vector>
#include <map>
#include <stdexcept>


template<typename T>
class mmatrix
{
public:

    class index
    {
      public:
        index& put(const int& p) { _index.push_back(p); return *this; }
        int size() const {return _index.size(); }
        void reset(){_index.clear();}
        // a map kulcstípusának rendelkeznie kell egy operator< művelettel:
        bool operator<(const index& i2) const
        {
            return lexicographical_compare(_index.begin(),_index.end(),
                                      i2._index.begin(),i2._index.end());
        }
      private:
        std::vector<int> _index;
    };

    // gsd: az iterator alapötlete nem volt rossz, de ahelyett, 
    // hogy újraírjuk az összes metódust, inkább örököljük :-)
    class iterator : public std::map<index,T>::iterator
    {
    public:
        // ezt azért, hogy rövidítsünk:
        typedef typename std::map<index,T>::iterator base_iterator;
        iterator( const base_iterator& bi) : base_iterator(bi) {}
        // ez az a művelet, amit át kellett írnuk:
        T& operator*() { return base_iterator::operator*().second; }
    };
    // ugyanez a const_iterator-ral.
    class const_iterator : public std::map<index,T>::const_iterator
    {
    public:
        typedef typename std::map<index,T>::const_iterator base_iterator;
        const_iterator( const base_iterator& bi) : base_iterator(bi) {}
        // ez azért kell, hogy az iterator konvertálódjon const_iterator-rá. 
        // beszúrtam egy plusz tesztet a főprogram végére.
        const_iterator( const iterator& i) : base_iterator(i) {}
        const T& operator*() { return base_iterator::operator*().second; }
    };

    mmatrix(const index& ix);
    int dim() const { return _dim; } 

    iterator begin();  //a matrix elejere mutato iteratort adja vissza
    iterator end();    //a matrix vegere mutato
    const_iterator begin() const;   // kellenek a const_iterator verziok is
    const_iterator end() const;

    T& at(const index& ix);
    T  at(const index& ix) const;

    T& operator[](const index& ix);
    T  operator[](const index& ix) const;

private:
    std::map<index,T> m;
    int _dim;

    void check_range(const index& ix) const;
};

template<typename T>
mmatrix<T>::mmatrix(const index& i)
{
    _dim=i.size();
}

// gsd: ez még annál is bonyolultabb, mint eredetileg gondoltam :-)
//
// 1. referenciát kellene visszaadnunk az at()-ben, de T() temporális,
//    nem tudjuk visszaadni referenciaként.
// 2. a defval-os megoldás sem jó, mert m.at(x) és m.at(y) ugyanaz
//    a defval objektum lenne, akkor is, ha x != y. Ez nem szerencsés.
// 3. marad a legegyszerűbb: csináljunk úgy, mint az std::map operator[]
//    a nemlétező elemeket létrehozzuk.
// 4. persze, ha "nemlétező" elemet adunk vissza, mint T(), akkor azt
//    másolnunk kell, nem hivatkozhatunk rá referenciaként
// 5. apróság: az operator[] szokásosan nem ellenőriz semmit, 
//    ezért az at() hívja az operator[]-t és nem fordítva.

template<typename T>
T& mmatrix<T>::operator[]( const index& ix)
{
    return m[ix];   // néha az egyszerűnél nincsen jobb
                    // létrehozza az elemet, ha nem létezett.
}
template<typename T>
T mmatrix<T>::operator[]( const index& ix) const
{
    // itt muszály körbejárni a dolgot
    // mert map::operator[] -nak nincsen const verziója
    typename std::map<index,T>::const_iterator ci = m.find(ix);
    if ( m.end() != ci )
        return ci->second;
    else
        return T();
}

template<typename T>
void mmatrix<T>::check_range(const index& ix) const
{ 
    //ha nem a matrix dimenziojanak megfelelo indexu ertekre hivatkozik
    if( ix.size() != _dim ) 
    {
        throw std::out_of_range("bad index");         //kivetelt dob
    }
}
template<typename T>
T& mmatrix<T>::at(const index& ix)
{ 
    check_range(ix);
    return operator[](ix);
}
template<typename T>
T mmatrix<T>::at(const index& i) const
{ 
    check_range(ix);
    return operator[](ix);
}

template<typename T>
typename mmatrix<T>::iterator mmatrix<T>::begin()
{
    return iterator(m.begin());
}
template<typename T>
typename mmatrix<T>::iterator mmatrix<T>::end()
{
    return iterator(m.end());
}
template<typename T>
typename mmatrix<T>::const_iterator mmatrix<T>::begin() const
{
    return const_iterator(m.begin());
}
template<typename T>
typename mmatrix<T>::const_iterator mmatrix<T>::end() const
{
    return const_iterator(m.end());
}

#endif