package org.snmp4j.agent.tutorial.impl;

import org.snmp4j.agent.mo.*;
import org.snmp4j.agent.tutorial.Snmp4jAgentTutorialMib;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

/**
 * The {@link Snmp4JAgentTutorialFileTreeBUModel} implements a virtual table instrumentation
 * using the SNMP4J-AgentX {@link BufferedMOTableModel}.
 *
 * @author Frank Fock
 */
public class Snmp4JAgentTutorialFileTreeBUModel
        extends BufferedMOMutableTableModel<Snmp4jAgentTutorialMib.Snmp4jAgentTutorialFileTreeBUEntryRow> {

    private MOScalar<OctetString> rootPathScalar;

    public Snmp4JAgentTutorialFileTreeBUModel(OID tableOID, MOTableIndex indexDef, MOColumn[] columns) {
        super(null);
        maxBufferSize = 1000;
        this.rootPathScalar = rootPathScalar;
    }

    public void setRootPathScalar(MOScalar<OctetString> rootPathScalar) {
        this.rootPathScalar = rootPathScalar;
    }

    protected File getRootDir() {
        if (rootPathScalar != null) {
            return new File(rootPathScalar.getValue().toString());
        }
        return new File(".");
    }

     @Override
    public int getRowCount() {
         Path root = getRootDir().toPath();
         try {
             long count = Files.walk(root).count();
             return (int)count;
         }
         catch (IOException iox) {
             iox.printStackTrace();
         }
         return 1;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public boolean containsRow(OID oid) {
        return (fetchRow(oid) != null);
    }

    @Override
    public OID lastIndex() {
        return getLastFileIndexInDepthFirstOrder(getRootDir(), new OID("1"));
    }

    @Override
    public OID firstIndex() {
        return new OID("1");
    }

    @Override
    protected Variable[] fetchRow(OID oid) {
        File f = getFileForOID(getRootDir(), oid);
        if (f == null) {
            return null;
        }
        try {
            return Snmp4JAgentTutorialFileTreeRowHelper.createRowFromFile(f);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    protected List<Snmp4jAgentTutorialMib.Snmp4jAgentTutorialFileTreeBUEntryRow> fetchNextRows(OID oid, int chunkSize) {
        File startFile = getRootDir();
        try {
            final OID index = new OID("1");
            final List<Snmp4jAgentTutorialMib.Snmp4jAgentTutorialFileTreeBUEntryRow> rows = new ArrayList<>(chunkSize);
            SimpleFileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    index.append(1);
                    if (index.compareTo(oid) > 0) {
                        rows.add(createRow(new OID(index), Snmp4JAgentTutorialFileTreeRowHelper.createRowFromPath(dir)));
                    }
                    if (rows.size() >= chunkSize) {
                        return FileVisitResult.TERMINATE;
                    }
                    return super.preVisitDirectory(dir, attrs);
                }

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    index.set(index.size()-1, index.last()+1);
                    if (index.compareTo(oid) > 0) {
                        rows.add(createRow(new OID(index), Snmp4JAgentTutorialFileTreeRowHelper.createRowFromPath(file)));
                    }
                    if (rows.size() >= chunkSize) {
                        return FileVisitResult.TERMINATE;
                    }
                    return super.visitFile(file, attrs);
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                    index.trim(1);
                    return super.postVisitDirectory(dir, exc);
                }
            };
            Files.walkFileTree(startFile.toPath(), visitor);
            return rows;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private OID getLastFileIndexInDepthFirstOrder(File rootDir, OID rootOID) {
        if (rootDir.isDirectory()) {
            File[] files = rootDir.listFiles();
            if (files != null && files.length > 0) {
                return getLastFileIndexInDepthFirstOrder(files[files.length - 1], new OID(rootOID.getValue(), files.length - 1));
            }
        }
        return rootOID;
    }

    private File getFileForOID(File rootDir, OID fileIndex) {
        if (fileIndex.size() == 0 || fileIndex.equals(firstIndex()) ||
                (fileIndex.size() > 0 && fileIndex.get(1) == 0)) {
            return rootDir;
        }
        OID remainingIndex = new OID(fileIndex.getValue(), 1, fileIndex.size()-1);
        if (remainingIndex.size() > 0 && rootDir.isDirectory()) {
            // TODO: 20.06.2016 Use Files.walk to read only those files until index (might be faster)?
            File[] files = rootDir.listFiles();
            if (files != null && files.length > remainingIndex.get(0)) {
                return getFileForOID(files[remainingIndex.get(0)-1], remainingIndex);
            }
        }
        return rootDir;
    }

    @Override
    protected void deleteAllRows() {

    }

    @Override
    protected void bulkDeleteRows(List<OID> indexList) {

    }

    @Override
    protected void insertRow(OID index, Variable[] values) {

    }

    @Override
    protected void writeRow(OID index, Variable[] values) {

    }

    @Override
    protected void deleteRow(OID index) {

    }

    @Override
    public Snmp4jAgentTutorialMib.Snmp4jAgentTutorialFileTreeBUEntryRow createRow(OID oid, Variable[] variables) throws UnsupportedOperationException {
        return rowFactory.createRow(oid, variables);
    }

    @Override
    public void freeRow(Snmp4jAgentTutorialMib.Snmp4jAgentTutorialFileTreeBUEntryRow snmp4jAgentTutorialFileTreeBUEntryRow) {
    }
}
