/*
 *
 *   Copyright 2007-2008 University Of Southern California
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing,
 *   software distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */
package edu.isi.pegasus.planner.catalog.site.classes;

import edu.isi.pegasus.common.util.Currently;
import edu.isi.pegasus.planner.catalog.site.classes.Directory.TYPE;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Prints the Site Catalog compatible with Site Catalog schema version 3
 *
 * <p>https://pegasus.isi.edu/wms/docs/schemas/sc-4.0/sc-3.0.html
 *
 * @author Rajiv Mayani
 * @version $Revision: 5858 $
 */
public class XML3PrintVisitor extends AbstractXMLPrintVisitor {

    /** The "official" namespace URI of the site catalog schema. */
    public static final String SCHEMA_NAMESPACE = "https://pegasus.isi.edu/schema/sitecatalog";

    /** The "not-so-official" location URL of the DAX schema definition. */
    public static final String SCHEMA_LOCATION = "https://pegasus.isi.edu/schema/sc-3.0.xsd";

    /** The version to report. */
    public static final String SCHEMA_VERSION = "3.0";

    /** @author mayani */
    private enum DirectoryTypes {
        HEADFS_SCRATCH,
        HEADFS_STORAGE,
        WORKERFS_SCRATCH,
        WORKERFS_STORAGE;
    }

    /** Keep track of which directories are found in the site. */
    private Directory[] mDirectory = null;

    /** Keep track of which file servers are listed in the specified directories. */
    private List<FileServer>[] mFileServer = null;

    /** Track which type of directory was traversed last. */
    private TYPE mLastDirectoryTraversed;

    /** Ensure that Directory information is written only once. */
    private boolean isFSWritten = false;

    /**
     * Visit the SiteStore object
     *
     * @param store the site store
     */
    public void visit(SiteStore store) throws IOException {
        String indent = getCurrentIndent();

        // write out the xml element
        mWriter.write(indent);
        indent = (indent != null && indent.length() > 0) ? indent : "";

        mWriter.write(indent);
        mWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        mWriter.write(mNewLine);

        mWriter.write(indent);
        mWriter.write("<!-- generated: ");
        mWriter.write(Currently.iso8601(false));
        mWriter.write(" -->");
        mWriter.write(mNewLine);

        // who generated this document
        mWriter.write(indent);
        mWriter.write("<!-- generated by: ");
        mWriter.write(System.getProperties().getProperty("user.name", "unknown"));
        mWriter.write(" [");
        mWriter.write(System.getProperties().getProperty("user.region", "??"));
        mWriter.write("] -->");
        mWriter.write(mNewLine);

        // root element with elementary attributes
        mWriter.write(indent);
        mWriter.write('<');

        mWriter.write("sitecatalog xmlns");
        mWriter.write("=\"");
        mWriter.write(SCHEMA_NAMESPACE);
        mWriter.write(
                "\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"");
        mWriter.write(SCHEMA_NAMESPACE);
        mWriter.write(' ');
        mWriter.write(SCHEMA_LOCATION);
        mWriter.write('"');
        writeAttribute(mWriter, "version", SCHEMA_VERSION);
        mWriter.write('>');
        mWriter.write(mNewLine);

        incrementIndentIndex();
    }

    /**
     * Depart the Site Store object.
     *
     * @param store the SiteStore
     */
    public void depart(SiteStore store) throws IOException {
        decrementIndentIndex();
        closeElement("sitecatalog");
    }

    /**
     * Visit the Site CatalogEntry object
     *
     * @param entry the site catalog entry
     * @throws IOException in case of problem of writing
     */
    public void visit(SiteCatalogEntry entry) throws IOException {
        String indent = getCurrentIndent();

        // write out the xml element
        mWriter.write(indent);
        mDirectory = new Directory[4];
        mFileServer = new List[4];
        isFSWritten = false;

        mWriter.write("<site ");
        writeAttribute(mWriter, "handle", entry.getSiteHandle());
        writeAttribute(mWriter, "arch", entry.getArchitecture().toString());
        writeAttribute(mWriter, "os", entry.getOS().toString());

        String val = null;
        if ((val = entry.getOSRelease()) != null && val.length() > 0) {
            writeAttribute(mWriter, "osrelease", val);
        }

        if ((val = entry.getOSVersion()) != null && val.length() > 0) {
            writeAttribute(mWriter, "osversion", val);
        }

        if ((val = entry.getGlibc()) != null && val.length() > 0) {
            writeAttribute(mWriter, "glibc", val);
        }

        mWriter.write(">");
        mWriter.write(mNewLine);

        // for our nested elements we have to increment the index
        incrementIndentIndex();
    }

    /**
     * Depart the Site Catalog Entry object.
     *
     * @param entry the site catalog entry
     */
    public void depart(SiteCatalogEntry entry) throws IOException {
        String indent = getCurrentIndent();

        if (!isFSWritten) {
            writeFS();
            isFSWritten = true;
        }

        entry.getProfiles().toXML(mWriter, indent);

        closeElement("site");
    }

    private void writeFS() throws IOException {
        writeHeadFS();
        writeWorkerFS();
    }

    private void writeHeadFS() throws IOException {
        if (mDirectory[DirectoryTypes.HEADFS_STORAGE.ordinal()] != null
                || mDirectory[DirectoryTypes.HEADFS_SCRATCH.ordinal()] != null
                || mDirectory[DirectoryTypes.WORKERFS_STORAGE.ordinal()] != null) {

            String indent = getCurrentIndent();

            mWriter.write(indent);
            mWriter.write("<head-fs>");
            mWriter.write(mNewLine);
            incrementIndentIndex();

            writeHeadFSScratch();
            writeHeadFSStorage();

            decrementIndentIndex();
            mWriter.write(indent);
            mWriter.write("</head-fs>");
            mWriter.write(mNewLine);
        }
    }

    private void writeHeadFSScratch() throws IOException {
        String indent = getCurrentIndent();

        // write out the xml element
        mWriter.write(indent);
        mWriter.write("<scratch>");
        mWriter.write(mNewLine);

        Directory directory = mDirectory[DirectoryTypes.HEADFS_SCRATCH.ordinal()];

        incrementIndentIndex();
        indent = getCurrentIndent();
        // write out the xml element
        mWriter.write(indent);
        mWriter.write("<shared>");
        mWriter.write(mNewLine);

        incrementIndentIndex();
        indent = getCurrentIndent();
        writeFileServer(directory, DirectoryTypes.HEADFS_SCRATCH);
        // write out the internal mount point
        directory.getInternalMountPoint().toXML(mWriter, indent);
        decrementIndentIndex();
        indent = getCurrentIndent();

        mWriter.write(indent);
        mWriter.write("</shared>");
        mWriter.write(mNewLine);

        decrementIndentIndex();
        indent = getCurrentIndent();

        mWriter.write(indent);
        mWriter.write("</scratch>");
        mWriter.write(mNewLine);
    }

    private void writeFileServer(Directory directory, DirectoryTypes type) throws IOException {
        String indent = getCurrentIndent();

        // iterate through all the file servers
        List<FileServer> servers = mFileServer[type.ordinal()];

        for (FileServer server : servers) {
            // write out the xml element
            mWriter.write(indent);
            mWriter.write("<file-server");

            writeAttribute(mWriter, "protocol", server.getProtocol());
            writeAttribute(mWriter, "url", server.getURLPrefix());
            writeAttribute(mWriter, "mount-point", server.getMountPoint());

            if (server.getProfiles().isEmpty()) {
                mWriter.write("/>");
            } else {
                mWriter.write(">");
                mWriter.write(mNewLine);

                incrementIndentIndex();
                indent = getCurrentIndent();
                server.getProfiles().toXML(mWriter, indent);
                decrementIndentIndex();
                indent = getCurrentIndent();

                mWriter.write(indent);
                mWriter.write("</file-server>");
            }
            mWriter.write(mNewLine);
        }
    }

    private void writeHeadFSStorage() throws IOException {
        String indent = getCurrentIndent();

        // write out the xml element
        mWriter.write(indent);
        mWriter.write("<storage>");
        mWriter.write(mNewLine);

        Directory directory = mDirectory[DirectoryTypes.HEADFS_STORAGE.ordinal()];

        if (directory != null) {
            incrementIndentIndex();
            indent = getCurrentIndent();
            // write out the xml element
            mWriter.write(indent);
            mWriter.write("<shared>");
            mWriter.write(mNewLine);

            incrementIndentIndex();
            indent = getCurrentIndent();
            writeFileServer(directory, DirectoryTypes.HEADFS_STORAGE);
            // write out the internal mount point
            directory.getInternalMountPoint().toXML(mWriter, indent);
            decrementIndentIndex();
            indent = getCurrentIndent();

            mWriter.write(indent);
            mWriter.write("</shared>");
            mWriter.write(mNewLine);

            decrementIndentIndex();
            indent = getCurrentIndent();
        }

        // Local
        directory = mDirectory[DirectoryTypes.WORKERFS_STORAGE.ordinal()];

        if (directory != null) {
            incrementIndentIndex();
            indent = getCurrentIndent();
            // write out the xml element
            mWriter.write(indent);
            mWriter.write("<local>");
            mWriter.write(mNewLine);

            incrementIndentIndex();
            indent = getCurrentIndent();
            writeFileServer(directory, DirectoryTypes.WORKERFS_STORAGE);
            // write out the internal mount point
            directory.getInternalMountPoint().toXML(mWriter, indent);
            decrementIndentIndex();
            indent = getCurrentIndent();

            mWriter.write(indent);
            mWriter.write("</local>");
            mWriter.write(mNewLine);

            decrementIndentIndex();
            indent = getCurrentIndent();
        }

        mWriter.write(indent);
        mWriter.write("</storage>");
        mWriter.write(mNewLine);
    }

    private void writeWorkerFS() throws IOException {
        String indent = getCurrentIndent();

        Directory directory = mDirectory[DirectoryTypes.WORKERFS_SCRATCH.ordinal()];

        if (directory != null) {
            // write out the xml element
            mWriter.write(indent);
            mWriter.write("<worker-fs>");
            mWriter.write(mNewLine);

            incrementIndentIndex();
            writeWorkerFSScratch();
            decrementIndentIndex();

            mWriter.write(indent);
            mWriter.write("</worker-fs>");
            mWriter.write(mNewLine);
        }
    }

    private void writeWorkerFSScratch() throws IOException {
        String indent = getCurrentIndent();
        Directory directory = mDirectory[DirectoryTypes.WORKERFS_SCRATCH.ordinal()];

        // write out the xml element
        mWriter.write(indent);
        mWriter.write("<scratch>");
        mWriter.write(mNewLine);

        incrementIndentIndex();
        indent = getCurrentIndent();
        // write out the xml element
        mWriter.write(indent);
        mWriter.write("<local>");
        mWriter.write(mNewLine);

        incrementIndentIndex();
        indent = getCurrentIndent();
        writeFileServer(directory, DirectoryTypes.WORKERFS_SCRATCH);
        // write out the internal mount point
        directory.getInternalMountPoint().toXML(mWriter, indent);
        decrementIndentIndex();
        indent = getCurrentIndent();

        mWriter.write(indent);
        mWriter.write("</local>");
        mWriter.write(mNewLine);

        decrementIndentIndex();
        indent = getCurrentIndent();

        mWriter.write(indent);
        mWriter.write("</scratch>");
        mWriter.write(mNewLine);
    }

    /**
     * Visit the GridGateway object
     *
     * @param gateway the grid gateway
     * @throws IOException in case of error while writing to underlying stream
     */
    public void visit(GridGateway gateway) throws IOException {
        String indent = getCurrentIndent();

        // write out the xml element
        mWriter.write(indent);
        mWriter.write("<grid ");
        writeAttribute(mWriter, "type", gateway.getType().toString());
        writeAttribute(mWriter, "contact", gateway.getContact());
        writeAttribute(mWriter, "scheduler", gateway.getScheduler().toString());
        writeAttribute(mWriter, "jobtype", gateway.getJobType().toString());

        if (gateway.getOS() != null) {
            writeAttribute(mWriter, "os", gateway.getOS().toString());
        }
        if (gateway.getArchitecture() != null) {
            writeAttribute(mWriter, "arch", gateway.getArchitecture().toString());
        }

        String val = null;
        if ((val = gateway.getOSRelease()) != null) {
            writeAttribute(mWriter, "osrelease", val);
        }

        if ((val = gateway.getOSVersion()) != null) {
            writeAttribute(mWriter, "osversion", val);
        }

        if ((val = gateway.getGlibc()) != null) {
            writeAttribute(mWriter, "glibc", val);
        }

        if (gateway.getIdleNodes() != -1) {
            writeAttribute(mWriter, "idle-nodes", Integer.toString(gateway.getIdleNodes()));
        }

        if (gateway.getTotalNodes() != -1) {
            writeAttribute(mWriter, "total-nodes", Integer.toString(gateway.getTotalNodes()));
        }

        mWriter.write("/>");
        mWriter.write(mNewLine);
    }

    /**
     * Depart the GridGateway object
     *
     * @param entry GridGateway object
     * @throws IOException in case of error while writing to underlying stream
     */
    public void depart(GridGateway entry) throws IOException {}

    /**
     * Visit the directory object
     *
     * @param directory the directory
     * @throws IOException in case of error while writing to underlying stream
     */
    public void visit(Directory directory) throws IOException {
        // sanity check?
        if (directory.isEmpty()) {
            return;
        }

        switch (directory.getType()) {
            case shared_scratch:
                mLastDirectoryTraversed = TYPE.shared_scratch;
                mDirectory[DirectoryTypes.HEADFS_SCRATCH.ordinal()] = directory;
                break;
            case shared_storage:
                mLastDirectoryTraversed = TYPE.shared_storage;
                mDirectory[DirectoryTypes.HEADFS_STORAGE.ordinal()] = directory;
                break;
            case local_scratch:
                mLastDirectoryTraversed = TYPE.local_scratch;
                mDirectory[DirectoryTypes.WORKERFS_SCRATCH.ordinal()] = directory;
                break;
            case local_storage:
                mLastDirectoryTraversed = TYPE.local_storage;
                mDirectory[DirectoryTypes.WORKERFS_STORAGE.ordinal()] = directory;
                break;
            default:
                break;
        }
    }

    /**
     * Depart the shared directory
     *
     * @param directory the directory
     * @throws IOException in case of error while writing to underlying stream
     */
    public void depart(Directory directory) throws IOException {}

    /**
     * Visit FileServer site data object
     *
     * @param server the object corresponding to the FileServer
     * @throws IOException in case of error while writing to underlying stream
     */
    public void visit(FileServer server) throws IOException {

        if (mLastDirectoryTraversed == null) {
            // Should never happen
        }

        if (mFileServer[mLastDirectoryTraversed.ordinal()] == null) {
            mFileServer[mLastDirectoryTraversed.ordinal()] = new ArrayList<FileServer>();
        }

        mFileServer[mLastDirectoryTraversed.ordinal()].add(server);
    }

    /**
     * Depart the Directory object
     *
     * @param server the object corresponding to the FileServer
     * @throws IOException in case of error while writing to underlying stream
     */
    public void depart(FileServer server) throws IOException {}

    /**
     * Visit the ReplicaCatalog object
     *
     * @param catalog the object describing the catalog
     * @throws IOException in case of error while writing to underlying stream
     */
    public void visit(ReplicaCatalog catalog) throws IOException {
        String indent = this.getCurrentIndent();

        if (!isFSWritten) {
            writeFS();
            isFSWritten = true;
        }

        // write out the xml element
        mWriter.write(indent);
        mWriter.write("<replica-catalog ");

        // fixed for time being
        writeAttribute(mWriter, "type", catalog.getType());
        writeAttribute(mWriter, "url", catalog.getURL());

        mWriter.write(">");
        mWriter.write(mNewLine);

        // for our nested elements we have to increment the index
        incrementIndentIndex();
    }

    /**
     * Depart the ReplicaCatalog object
     *
     * @param catalog the object describing the catalog
     * @throws IOException in case of error while writing to underlying stream
     */
    public void depart(ReplicaCatalog catalog) throws IOException {
        String indent = getCurrentIndent();

        // list all the aliases first
        for (Iterator<String> it = catalog.getAliasIterator(); it.hasNext(); ) {
            catalog.writeAlias(mWriter, indent, it.next());
        }

        closeElement("replica-catalog");
    }

    /**
     * Visit the connection object
     *
     * @param c the connection.
     * @throws IOException in case of error while writing to underlying stream
     */
    public void visit(Connection c) throws IOException {
        String indent = this.getCurrentIndent();

        // write out the xml element
        // write out the xml element
        mWriter.write(indent);
        mWriter.write("<connection ");

        writeAttribute(mWriter, "key", c.getKey());

        mWriter.write(">");
        mWriter.write(c.getValue());
        mWriter.write("</connection>");
        mWriter.write(mNewLine);

        // for our nested elements we have to increment the index
        incrementIndentIndex();
    }

    /**
     * Depart the connection object
     *
     * @param c the connection.
     * @throws IOException in case of error while writing to underlying stream
     */
    public void depart(Connection c) throws IOException {
        closeElement("connection");
    }
}
