/*
 * Copyright (C) 2013 Canonical, Ltd.
 *
 * Authors:
 *    Jussi Pakkanen <jussi.pakkanen@canonical.com>
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of version 3 of the GNU General Public License as published
 * by the Free Software Foundation.
 *
 * This library 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, see <http://www.gnu.org/licenses/>.
 */

#include "Scanner.hh"
#include "../extractor/DetectedFile.hh"
#include "../extractor/MetadataExtractor.hh"
#include "../mediascanner/internal/utils.hh"
#include<cstdio>
#include<cstdlib>
#include<filesystem>
#include<memory>
#include<cassert>
#include<glib.h>

using namespace std;

namespace mediascanner {

struct Scanner::Private {
    Private(MetadataExtractor *extractor_, const std::string &root, const MediaType type_);

    string curdir;
    filesystem::directory_iterator dir;
    vector<string> dirs;
    MediaType type;
    MetadataExtractor *extractor;
};

Scanner::Private::Private(MetadataExtractor *extractor, const std::string &root, const MediaType type) :
        type(type),
        extractor(extractor)
{
    dirs.push_back(root);
}

Scanner::Scanner(MetadataExtractor *extractor, const std::string &root, const MediaType type) :
    p(new Scanner::Private(extractor, root, type)) {
}

Scanner::~Scanner() {
    delete p;
}


DetectedFile Scanner::next() {
begin:
    error_code ec = error_code();
    while(p->dir == filesystem::directory_iterator()) {
        if(p->dirs.empty()) {
            throw StopIteration();
        }
        p->curdir = p->dirs.back();
        p->dirs.pop_back();
        p->dir = filesystem::directory_iterator(p->curdir, ec);
        if(ec) {
            g_debug("Directory %s is inaccessible (%s), skipping it (%s).",
                    p->curdir.c_str(), ec.message().c_str(), __PRETTY_FUNCTION__);
            p->dir = filesystem::directory_iterator();
            continue;
        }
        if(is_rootlike(p->curdir)) {
            g_debug("Directory %s looks like a top level root directory, skipping it (%s).",
                    p->curdir.c_str(), __PRETTY_FUNCTION__);
            p->dir = filesystem::directory_iterator();
            continue;
        }
        if(has_scanblock(p->curdir)) {
            g_debug("Directory %s has a scan block file, skipping it.",
                    p->curdir.c_str());
            p->dir = filesystem::directory_iterator();
            continue;
        }
        g_debug("In subdir %s", p->curdir.c_str());
    }

    while (p->dir != filesystem::end(p->dir)) {
        const auto& entry = *(p->dir);
        const auto fname = entry.path().filename().string();
        if(fname[0] == '.') { // Ignore hidden files and dirs.
            ++p->dir;
            continue;
        }
        string fullpath = p->curdir + "/" + fname;
        if(entry.is_regular_file()) {
            try {
                DetectedFile d = p->extractor->detect(fullpath);
                if (p->type == AllMedia || d.type == p->type) {
                    ++p->dir;
                    return d;
                }
            } catch (const exception &e) {
                /* Ignore non-media files */
            }
        } else if(entry.is_directory()) {
            p->dirs.push_back(fullpath);
        }
        ++p->dir;
    }

    // Nothing left in this directory so on to the next.
    p->dir = filesystem::directory_iterator();
    p->curdir.clear();
    // This should be just return next(s) but we can't guarantee
    // that GCC can optimize away the tail recursion so we do this
    // instead. Using goto instead of wrapping the whole function body in
    // a while(true) to avoid the extra indentation.
    goto begin;
}

}
