/* pathtree.hh
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2009-2019 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifndef PATHTREE
#define PATHTREE

#include <list>
#include <string>

template < class T >
class PathTree
{
public:
    PathTree( const std::string &name,
              const T &data = T() ) : m_name( name ),
                                      m_node_data( data ),
                                      m_is_exact_hit( false )
    {
    }

    const std::string &getName() const
    {
        return m_name;
    }

    bool getIsExactHit() const
    {
        return m_is_exact_hit;
    }

    void setIsExactHit( bool nv )
    {
        m_is_exact_hit = nv;
    }

    void addPath( const std::string &path,
                  const T &data,
                  std::string::size_type start_pos = 0 )
    {
        std::string::size_type slash = path.find( '/', start_pos );
        
        if ( slash == start_pos ) {
            slash = path.find( '/', start_pos + 1 );
        }

        if ( slash == std::string::npos ) {
            std::string basename = std::string( path, start_pos );
            if ( basename.length() > 0 ) {

                typename std::list< PathTree<T> >::iterator it1;
                for ( it1 = m_children.begin();
                      it1 != m_children.end();
                      it1++ ) {
                    if ( it1->getName() == basename ) break;
                }
                if ( it1 == m_children.end() ) {
                    m_children.push_back( PathTree<T>( basename, data ) );
                    m_children.back().setIsExactHit( true );
                } else {
                    it1->setIsExactHit( true );
                    it1->setNodeData( data );
                }
            }
        } else {
            std::string basename = std::string( path, start_pos, slash - start_pos );
            if ( basename.length() > 0 && basename != "/" ) {
                typename std::list< PathTree<T> >::iterator it1;
                for ( it1 = m_children.begin();
                      it1 != m_children.end();
                      it1++ ) {
                    if ( it1->getName() == basename ) break;
                }
                if ( it1 == m_children.end() ) {
                    m_children.push_back( PathTree<T>( basename ) );
                    m_children.back().addPath( path, data, slash );
                } else {
                    it1->addPath( path, data, slash );
                }
            } else {
                addPath( path, data, slash );
            }
        }
    }

//     void print( int depth = 0 )
//     {
//         for ( std::list< PathTree<T> >::iterator it1 = m_children.begin();
//               it1 != m_children.end();
//               it1++ ) {
//             std::cout << std::string( depth * 2, ' ' ) << it1->getName() << std::endl;
//             it1->print( depth + 1);
//         }
//     }

    const PathTree<T> *findNode( const std::string &path, std::string::size_type start_pos = 0 ) const
    {
        std::string::size_type slash = path.find( '/', start_pos );
        
        if ( slash == start_pos ) {
            slash = path.find( '/', start_pos + 1 );
        }

        int basename_length;

        if ( slash == std::string::npos ) {
            basename_length = path.length() - start_pos;
        } else {
            basename_length = slash - start_pos;
        }

        if ( basename_length < 1 ) return this;

        if ( ! ( path[start_pos] == '/' && basename_length == 1 ) ) {
            typename std::list< PathTree<T> >::const_iterator it1;
            for ( it1 = m_children.begin();
                  it1 != m_children.end();
                  it1++ ) {
                if ( path.compare( start_pos, basename_length, it1->getName() ) == 0 ) {
                    if ( slash == std::string::npos ) {
                        return &( *it1 );
                    } else {
                        return it1->findNode( path, slash );
                    }
                }
            }

            return NULL;
        } else {
            if ( slash == std::string::npos ) {
                if ( path.compare( start_pos, basename_length, getName() ) == 0 ) {
                    return this;
                }
                return this;
            } else return findNode( path, slash );
        }
    }

    const PathTree<T> *outerFind( const std::string &path, bool exactHitsOnly, std::string::size_type start_pos = 0 ) const
    {
        std::string::size_type slash = path.find( '/', start_pos );
        
        if ( slash == start_pos ) {
            slash = path.find( '/', start_pos + 1 );
        }

        int basename_length;

        if ( slash == std::string::npos ) {
            basename_length = path.length() - start_pos;
        } else {
            basename_length = slash - start_pos;
        }

        if ( basename_length < 1 ) {
            if ( ! exactHitsOnly || getIsExactHit() ) {
                return this;
            } else {
                return NULL;
            }
        }

        if ( ! ( path[start_pos] == '/' && basename_length == 1 ) ) {
            typename std::list< PathTree<T> >::const_iterator it1;
            const PathTree<T> *candidate = NULL;

            for ( it1 = m_children.begin();
                  it1 != m_children.end();
                  it1++ ) {
                std::string s( path, start_pos, basename_length );
                if ( path.compare( start_pos, basename_length, it1->getName() ) == 0 ) {
                    if ( ! exactHitsOnly || it1->getIsExactHit() ) {
                        candidate = &( *it1 );
                    }

                    if ( slash == std::string::npos ) {
                        return candidate;
                    } else {
                        auto sub_node = it1->outerFind( path, exactHitsOnly, slash );

                        if ( sub_node ) {
                            return sub_node;
                        } else {
                            return candidate;
                        }
                    }
                }
            }

            return NULL;
        } else {
            const PathTree<T> *candidate = NULL;
            if ( ! exactHitsOnly  || getIsExactHit() ) {
                candidate = this;
            }

            if ( slash == std::string::npos ) {
            } else {
                auto sub_node = outerFind( path, exactHitsOnly, slash );
                if ( sub_node ) {
                    return sub_node;
                }
            }

            return candidate;
        }
    }

    void getNodeDataRecursiveForHits( std::list<T> &datas ) const
    {
        if ( getIsExactHit() == true ) {
            datas.push_back( m_node_data );
        }
        typename std::list< PathTree<T> >::const_iterator it1;
        for ( it1 = m_children.begin();
              it1 != m_children.end();
              it1++ ) {
            it1->getNodeDataRecursiveForHits( datas );
        }
    }

    T getNodeData() const
    {
        return m_node_data;
    }

    void setNodeData( const T &d )
    {
        m_node_data = d;
    }
private:
    std::list< PathTree<T> > m_children;
    std::string m_name;
    T m_node_data;
    bool m_is_exact_hit;
};

#endif
