/* deeppathstore.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2012-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
 */

#include "deeppathstore.hh"
#include "aguix/util.h"
#include <memory>

DeepPathStore::DeepPathStore()
{
}

void DeepPathStore::storePath( const std::string &path, time_t ts, DeepPathNode::node_change_t *changed, enum dps_ts_update ts_update  )
{
    storePath( m_root, path, ts, 0, changed, ts_update );
}

static void push_leafs( const DeepPathNode &node,
                        std::list< std::pair< std::string, time_t > > &l,
                        const std::string &prefix )
{
    std::string new_prefix( prefix );

    new_prefix += node.get_name();

    if ( node.get_children().empty() ) {
        if ( ! new_prefix.empty() ) {
            l.push_back( std::make_pair( new_prefix,
                                         node.get_ts() ) );
        }
    } else {
        for ( std::list< DeepPathNode >::const_iterator it1 = node.get_children().begin();
              it1 != node.get_children().end();
              it1++ ) {
            push_leafs( *it1, l, new_prefix );
        }
    }
}

std::list< std::pair< std::string, time_t > > DeepPathStore::getPaths() const
{
    std::list< std::pair< std::string, time_t > > l;

    push_leafs( m_root, l, "" );

    return l;
}

static void walk_leafs( const DeepPathNode &node,
                        std::function< void( const std::string &, size_t, time_t ) > cb,
                        const std::string &prefix,
                        size_t depth )
{
    std::string new_prefix( prefix );

    new_prefix += node.get_name();

    if ( node.get_children().empty() ) {
        if ( ! new_prefix.empty() ) {
            cb( new_prefix, depth, node.get_ts() );
        }
    } else {
        for ( std::list< DeepPathNode >::const_iterator it1 = node.get_children().begin();
              it1 != node.get_children().end();
              it1++ ) {
            walk_leafs( *it1, cb, new_prefix, depth + 1 );
        }
    }
}

void DeepPathStore::walkPaths( std::function< void( const std::string &, size_t, time_t ) > cb )
{
    walk_leafs( m_root, cb, "", 0 );
}

void DeepPathStore::storePath( DeepPathNode &node,
                               const std::string &path,
                               time_t ts,
                               std::string::size_type start_pos,
                               DeepPathNode::node_change_t *changed,
                               enum dps_ts_update ts_update  )
{
    if ( start_pos == std::string::npos ) {
        if ( node.get_ts() != ts ) {
            if ( ts_update == DPS_TS_UPDATE_ALWAYS ||
                 ( ts_update == DPS_TS_UPDATE_NONZERO && ts != 0 ) ) {
                node.set_ts( ts );
                if ( changed ) {
                    if ( *changed == DeepPathNode::NODE_UNCHANGED ) {
                        *changed = DeepPathNode::NODE_UPDATED;
                    }
                }
            }
        }
        return;
    }

    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;

    std::string basename( path, start_pos, basename_length );

    if ( ! ( path[start_pos] == '/' && basename_length == 1 ) ) {
        storePath( node.lookup_or_insert( basename, ts, changed ),
                   path, ts, slash, changed, ts_update );
    } else {
        if ( slash != std::string::npos ) {
            storePath( node, path, ts, slash, changed, ts_update );
        }
    }
}

void DeepPathStore::clear()
{
    m_root = DeepPathNode();
}

void DeepPathStore::relocateEntries( const std::string &dest,
                                     const std::string &source,
                                     time_t ts,
                                     bool move,
                                     DeepPathNode::node_change_t *changed )
{
    DeepPathNode *node = NULL, *parent = NULL;
    std::shared_ptr< DeepPathNode > copied_node;
    DeepPathNode *dest_node = NULL;

    if ( findNode( source, &node, &parent ) != 0 ) {
        return;
    }

    copied_node = std::make_shared<DeepPathNode>( *node );
    if ( move ) {
        parent->remove( node->get_name() );

        if ( changed ) {
            *changed = DeepPathNode::NODE_MODIFIED;
        }
    }
    node = copied_node.get();

    // node is now either a pointer to the existing old node
    // or a pointer to a copy of the old node. In any case,
    // insert it with the dest path
    DeepPathNode::node_change_t temp_change = DeepPathNode::NODE_UNCHANGED;
        
    if ( insertNode( dest, &dest_node, ts, &temp_change ) == 0 ) {

        if ( changed && temp_change != DeepPathNode::NODE_UNCHANGED ) {
            *changed = DeepPathNode::NODE_MODIFIED;
        }

        temp_change = DeepPathNode::NODE_UNCHANGED;

        copyChildren( dest_node, node, &temp_change );

        if ( changed && temp_change != DeepPathNode::NODE_UNCHANGED ) {
            *changed = DeepPathNode::NODE_MODIFIED;
        }
    }
}

int DeepPathStore::findNode( const std::string &path,
                             DeepPathNode **node_return,
                             DeepPathNode **parent_node_return )
{
    DeepPathNode *node = &m_root;
    DeepPathNode *parent = node;

    for ( std::string::size_type start_pos = 0;
          start_pos != std::string::npos; ) {
        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 ) break;

        std::string basename( path, start_pos, basename_length );
        start_pos = slash;

        auto child_node = node->lookup( basename );

        if ( ! child_node ) {
            node = NULL;
            break;
        }

        parent = node;
        node = child_node;
    }

    if ( node ) {
        if ( node_return ) {
            *node_return = node;
        }

        if ( parent_node_return ) {
            *parent_node_return = parent;
        }

        return 0;
    }

    return 1;
}

int DeepPathStore::insertNode( const std::string &path,
                               DeepPathNode **node_return,
                               time_t ts,
                               DeepPathNode::node_change_t *changed )
{
    std::vector< std::string > path_elements;
    DeepPathNode *node = &m_root;

    splitSegments( path, path_elements );

    for ( auto &p : path_elements ) {
        DeepPathNode::node_change_t temp_change = DeepPathNode::NODE_UNCHANGED;

        DeepPathNode &child_node = node->lookup_or_insert( p, ts, &temp_change );

        if ( changed && temp_change != DeepPathNode::NODE_UNCHANGED ) {
            *changed = DeepPathNode::NODE_INSERTED;
        }

        node = &child_node;
    }

    node->set_ts( ts );
    
    if ( node_return ) {
        *node_return = node;
    }

    return 0;
}

int DeepPathStore::copyChildren( DeepPathNode *dest_node,
                                 const DeepPathNode *source_node,
                                 DeepPathNode::node_change_t *changed )
{
    if ( ! dest_node || ! source_node ) return 1;

    for ( auto &c : source_node->get_children() ) {
        auto sub_node = dest_node->lookup( c.get_name() );

        if ( sub_node ) {
            // exists already, just copy children recursively
            copyChildren( sub_node, &c, changed );
        } else {
            // insert as new one
            dest_node->insert( c );

            if ( changed ) {
                *changed = DeepPathNode::NODE_INSERTED;
            }
        }
    }

    return 0;
}

void DeepPathStore::splitSegments( const std::string &path,
                                   std::vector< std::string > &path_segments )
{
    for ( std::string::size_type start_pos = 0;
          start_pos != std::string::npos; ) {
        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 ) break;

        std::string basename( path, start_pos, basename_length );

        start_pos = slash;

        path_segments.push_back( basename );
    }
}

bool DeepPathStore::removeEntry( const std::string &path )
{
    DeepPathNode *node = NULL, *parent = NULL;

    if ( findNode( path, &node, &parent ) != 0 ) {
        return false;
    }

    parent->remove( node->get_name() );

    return true;
}

bool DeepPathStore::empty() const
{
    return m_root.get_children().empty();
}
