/*
 *  This file is part of SWADroid.
 *
 *  Copyright (C) 2010 Juan Miguel Boyero Corral <juanmi1982@gmail.com>
 *
 *  SWADroid 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  SWADroid 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 SWADroid.  If not, see <http://www.gnu.org/licenses/>.
 */
package es.ugr.swad.swadroid.modules.downloads;

import android.content.Context;
import android.util.Log;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import es.ugr.swad.swadroid.Constants;


/**
 * Class used to navigate around the XML file. That XML file contains the
 * information of all the directory.
 *
 * @author Sergio Ropero Oliver. <sro0000@gmail.com>
 * @author Helena Rodriguez Gijon <hrgijon@gmail.com>
 * @author Juan Miguel Boyero Corral <juanmi1982@gmail.com>
 * @version 1.0
 */

//TODO look for a more efficient navigation
public class DirectoryNavigator {

    private String XMLinfo;

    private final ArrayList<String> path;

    private ArrayList<DirectoryItem> currentItems;
    /**
     * Application context
     */
    private Context mContext;

    /**
     * Downloads tag name for Logcat
     */
    private static final String TAG = Constants.APP_TAG + " DirectoryNavigator";

    /**
     * Constructor.
     *
     * @param fileXML File where we obtain all the information.
     */
    public DirectoryNavigator(Context ctx, String fileXML) {
        this.XMLinfo = fileXML;
        this.path = new ArrayList<>();
        this.mContext = ctx;
    }

    /**
     * Travel inside a subdirectory.
     *
     * @param subDirectory The subdirectory where we will travel.
     * @return Return a list of items that are inside the subdirectory.
     * @throws InvalidPathException When the directory don't exist.
     */
    public ArrayList<DirectoryItem> goToSubDirectory(String subDirectory) throws InvalidPathException {
        //We increase the path.
        path.add(subDirectory);

        Node node = goToDirectory();

        currentItems = new ArrayList<>(getItems(node));

        return currentItems;
    }


    public ArrayList<DirectoryItem> goToCurrentDirectory() {
        return currentItems;
    }

    /**
     * Travel inside a subdirectory.
     *
     * @param directoryPosition The position of the subdirectory where we will travel.
     * @return Return a list of items that are inside the subdirectory.
     * @throws InvalidPathException When the directory don't exist.
     */
    public ArrayList<DirectoryItem> goToSubDirectory(int directoryPosition) throws InvalidPathException {
        String subDirectory = currentItems.get(directoryPosition).getName();

        //We increase the path.
        path.add(subDirectory);

        Node node = goToDirectory();

        currentItems = new ArrayList<>(getItems(node));

        return currentItems;
    }

    /**
     * Travel to the parent directory.
     *
     * @return Return a list of items that are inside the parent directory.
     * @throws InvalidPathException When the directory does not exist.
     */
    public ArrayList<DirectoryItem> goToParentDirectory() throws InvalidPathException {

        if (path.size() != 0) {
            //We decrease the path.
            path.remove(path.size() - 1);
            Node node = goToDirectory();

            currentItems = new ArrayList<>(getItems(node));
        }

        return currentItems;
    }

    /**
     * Refresh the XML file and refresh the directory data. We throw an exception if the directory was erased.
     *
     * @return Return a list of items in the current directory.
     * @throws InvalidPathException When the directory don't exist.
     */
    public ArrayList<DirectoryItem> refresh(String fileXML) throws InvalidPathException {
        this.XMLinfo = fileXML;

        Node node = goToDirectory();

        currentItems = new ArrayList<>(getItems(node));
        return currentItems;
    }

    /**
     * Go to the root directory.
     *
     * @return The items of the root directory.
     * @throws InvalidPathException When the directory don't exist.
     */
    public ArrayList<DirectoryItem> goToRoot() throws InvalidPathException {
        path.clear();

        Node node = goToDirectory();

        currentItems = new ArrayList<>(getItems(node));
        return currentItems;
    }

    /**
     * Obtain all the items of the specific directory.
     *
     * @param node Node that represents the current directory.
     * @return Return a list of items of the directory passed as parameter.
     */
    private List<DirectoryItem> getItems(Node node) {
        List<DirectoryItem> items = new ArrayList<>();
        NodeList childs = node.getChildNodes();
        DirectoryItem item;

        for (int i = 0; i < childs.getLength(); i++) {
            Node currentChild = childs.item(i);
            NamedNodeMap attributes = currentChild.getAttributes();
            String name;

            switch(currentChild.getNodeName()) {
                case "dir":
                    name = attributes.getNamedItem("name").getNodeValue();
                    item = new DirectoryItem(name);
                    items.add(item);
                    break;
                case "file":
                    String type = "";
                    long fileCode = -1;
                    long size = -1; //In bytes
                    long time = -1;
                    String license = "";
                    String publisher = "";
                    String photo = "";

                    name = attributes.getNamedItem("name").getNodeValue();

                    //WE GET THE REST OF THE INFO
                    NodeList fileData = currentChild.getChildNodes();
                    for (int j = 0; j < fileData.getLength(); j++) {
                        Node data = fileData.item(j);
                        String tag = data.getNodeName();
                        Node firstChild = data.getFirstChild();
                        if (firstChild != null) {
                            switch (tag) {
                                case "code":
                                    fileCode = Long.valueOf(firstChild.getNodeValue());
                                    break;
                                case "time":
                                    time = Long.valueOf(firstChild.getNodeValue());
                                    break;
                                case "license":
                                    license = firstChild.getNodeValue();
                                    break;
                                case "publisher":
                                    publisher = firstChild.getNodeValue();
                                    break;
                                case "photo":
                                    photo = firstChild.getNodeValue();
                                    break;
                                case "size":
                                    size = Integer.parseInt(firstChild.getNodeValue());
                                    break;
                            }
                        }
                    }

                    item = new DirectoryItem(name, type, fileCode, size, time, license, publisher, photo);
                    items.add(item);
                    break;
            }
        }

        Collections.sort(items);
        
        return items;
    }

    /**
     * Go to the directory of the path.
     *
     * @return Return the node that correspond to the directory path.
     * @throws InvalidPathException When the directory don't exist.
     */
    private Node goToDirectory() throws InvalidPathException {
        //Instance of a DOM factory.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(false);

        //We create a parser
        DocumentBuilder builder;

        int directoryLevel = 0;
        Node currentNode = null;

        try {
            builder = factory.newDocumentBuilder();

            //We read the entire XML file.
            Document dom = builder.parse(new InputSource(new StringReader(XMLinfo)));

            //We put the current node in the root Element.
            currentNode = dom.getDocumentElement();

            //We change the current node.
            for (String aPath : path) {
                //WE GET THE REST OF THE INFO
                NodeList childs = currentNode.getChildNodes();
                for (int j = 0; j < childs.getLength(); j++) {
                    Node currentChild = childs.item(j);
                    if (currentChild.getNodeName().equals("dir") || currentChild.getNodeName().equals("file")) {
                        NamedNodeMap attributes = currentChild.getAttributes();
                        if (aPath.equals(attributes.getNamedItem("name").getNodeValue())) {
                            currentNode = currentChild;
                            directoryLevel++;
                        }
                    }
                }
            }
        } catch (ParserConfigurationException | IOException | SAXException ex) {
            Log.e(TAG, ex.getMessage(), ex);
        }

        //If we don't find the entire path, we throw an exception.
        if (directoryLevel != path.size()) {
            throw new InvalidPathException();
        } else {
            return currentNode;
        }
    }

    public String getPath() {
        String fullPath = "/";

        for (String aPath : path) {
            fullPath = fullPath + aPath + "/";
        }

        return fullPath;
    }

    /**
     * Function used for testing.
     *
     * @param directory Directory to add to the current path.
     */
    public void addToPath(String directory) {
        path.add(directory);
    }

    /**
     * Searches for a node in the current directory with the given name
     *
     * @param name Name of the node located on the current directory.
     * @return null in case it does not exists any node with the given name
     */
    private DirectoryItem getDirectoryItem(String name) {
        DirectoryItem node = null;

        boolean found = false;
        int i = 0;
        while (!found && i < currentItems.size()) {
            node = currentItems.get(i);
            String nameItem = node.getName();
            if (nameItem.compareTo(name) == 0) {
                found = true;
            } else {
                ++i;
            }
        }

        return node;
    }

    /**
     * Searches for a node in the current directory with the given position
     *
     * @param position position where the node is located
     * @return null in case it does not exists any node located at the given position
     */
    public DirectoryItem getDirectoryItem(int position) {
        DirectoryItem node = null;
        if (position >= 0 && position < currentItems.size()) {
            node = currentItems.get(position);
        }
        return node;
    }


    /**
     * Identifies the node with name @a name and gets its file code in case the node is a file. In case the node is a directory returns -1
     *
     * @param name Name of the node located on the current directory.
     * @return -1 in case the node is a directory or it does not exists any node with the given name
     * fileCode in case the node is a file
     */
    public long getFileCode(String name) {
        long fileCode = -1;

        DirectoryItem node = getDirectoryItem(name);
        if (node != null && !node.isFolder()) {
            fileCode = node.getFileCode();
        }

        return fileCode;

    }

    // TODO it should not be needed because name of the node and name of the file should be equal.
    public String getFileName(String name) {
        DirectoryItem node = getDirectoryItem(name);
        return node.getName();

    }
    
    public boolean isRootDirectory() {
    	return (path.size() == 0);
    }
}

/**
 * Class that represents an exception occurred because the path
 * are incorrect.
 *
 * @author Sergio Ropero Oliver.
 * @version 1.0
 */
class InvalidPathException extends IllegalArgumentException {
    private static final long serialVersionUID = 1L;
}