/*
 Copyright (C) 2001, 2006 United States Government as represented by
the Administrator of the National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.formats.rpf;

import gov.nasa.worldwind.*;

import java.io.*;
import java.util.*;
import java.util.concurrent.locks.*;
import static java.util.logging.Level.*;

/**
 * @author dcollins
 * @version $Id: RpfTocCrawler.java 1762 2007-05-07 19:43:55Z dcollins $
 */
public class RpfTocCrawler
{
    public static final String RPF_DIRECTORY = "RPF";
    public static final String RPF_OVERVIEW_EXTENSION = ".OVR";
    public static final String RPF_TOC_EXTENSION = ".TOC";

    public static interface RpfTocCrawlerListener
    {
        void fileFound(File tocFile);

        void finished();
    }

    public abstract static class RpfTocGrouper implements RpfTocCrawlerListener
    {
        private final RpfFramePropertyType groupType;

        public RpfTocGrouper(RpfFramePropertyType groupType)
        {
            if (groupType == null)
            {
                String message = WorldWind.retrieveErrMsg("nullValue.RpfFramePropertyTypeIsNull");
                WorldWind.logger().log(FINE, message);
                throw new IllegalArgumentException(message);
            }
            this.groupType = groupType;
        }

        public void fileFound(File tocFile)
        {
            RpfTocFile rpfTocFile = null;
            try
            {
                rpfTocFile = RpfTocFile.load(tocFile);
            }
            catch (IOException e)
            {
                WorldWind.logger().log(FINE, e.getMessage());
            }

            if (rpfTocFile == null)
                return;

            String firstFrameFilename = findFirstRpfFrameFilename(rpfTocFile);
            if (firstFrameFilename == null)
                return;

            RpfFrameProperties firstFrameProperties = null;
            try
            {
                firstFrameProperties = RpfFrameFilenameUtil.parseFilename(firstFrameFilename);
            }
            catch (Exception e)
            {
                String message = WorldWind.retrieveErrMsg("RpfTocCrawler.ExceptionParsingFilename");
                WorldWind.logger().log(FINE, message, e);
            }

            if (firstFrameProperties != null)
                this.addToGroup(this.groupType.getInstance(firstFrameProperties), rpfTocFile);
        }

        public void finished()
        {
        }

        public abstract void addToGroup(Object groupKey, RpfTocFile rpfTocFile);
    }

    private static class RpfTocRunner implements Runnable
    {
        private final RpfTocCrawler context;
        private final File directory;
        private final RpfTocCrawlerListener listener;

        public RpfTocRunner(RpfTocCrawler context, File directory, RpfTocCrawlerListener listener)
        {
            this.context = context;
            this.directory = directory;
            this.listener = listener;
        }

        public void run()
        {
            this.context.process(this.directory, listener, true);
            this.context.threadLock.lock();
            try
            {
                if (this.context.thread != this.context.deadThread)
                {
                    listener.finished();
                    this.context.thread = this.context.deadThread;
                }
            }
            finally
            {
                this.context.threadLock.unlock();
            }
        }
    }

    private final Thread deadThread = new Thread();
    private final Lock threadLock = new ReentrantLock();
    private volatile Thread thread = null;

    public RpfTocCrawler()
    {
    }

    public static String findFirstRpfFrameFilename(RpfTocFile tocFile)
    {
        if (tocFile != null
            && tocFile.getFrameFileIndexSection() != null
            && tocFile.getFrameFileIndexSection().getFrameFileIndexTable() != null
            && tocFile.getFrameFileIndexSection().getFrameFileIndexTable().size() > 0)
        {
            for (RpfFrameFileIndexSection.RpfFrameFileIndexRecord frameFileIndexRecord
                : tocFile.getFrameFileIndexSection().getFrameFileIndexTable())
            {
                if (frameFileIndexRecord != null
                    && frameFileIndexRecord.getFrameFileName() != null
                    && !frameFileIndexRecord.getFrameFileName().toUpperCase().endsWith(RPF_OVERVIEW_EXTENSION))
                {
                    return frameFileIndexRecord.getFrameFileName();
                }
            }
        }
        return null;
    }

    private void process(File file, RpfTocCrawlerListener listener, boolean inOwnThread)
    {
        this.threadLock.lock();
        try
        {
            if (inOwnThread && this.thread == deadThread)
                return;
        }
        finally
        {
            this.threadLock.unlock();
        }

        File[] children = file.listFiles();
        if (children == null)
            return;
        if (RPF_DIRECTORY.compareToIgnoreCase(file.getName()) == 0)
            this.processRpfDirectory(children, listener, inOwnThread);
        else
            this.processUnknownDirectory(children, listener, inOwnThread);
    }

    private void processRpfDirectory(File[] contents, RpfTocCrawlerListener listener, boolean inOwnThread)
    {
        for (File file : contents)
        {
            this.threadLock.lock();
            try
            {
                if (inOwnThread && this.thread == deadThread)
                    return;
            }
            finally
            {
                this.threadLock.unlock();
            }

            if (file.getName().toUpperCase().endsWith(RPF_TOC_EXTENSION))
                listener.fileFound(file);
        }
    }

    private void processUnknownDirectory(File[] contents, RpfTocCrawlerListener listener, boolean inOwnThread)
    {
        for (File file : contents)
        {
            this.threadLock.lock();
            try
            {
                if (inOwnThread && this.thread == deadThread)
                    return;
            }
            finally
            {
                this.threadLock.unlock();
            }

            if (file.isDirectory())
                this.process(file, listener, inOwnThread);
        }
    }

    public File[] invoke(File directory)
    {
        File validDir = this.validateDirectory(directory);
        final Collection<File> results = new ArrayList<File>();
        this.process(validDir, new RpfTocCrawlerListener()
        {
            public void fileFound(File file)
            {
                if (file.exists())
                    results.add(file);
            }

            public void finished()
            {
            }
        }, false);
        File[] tocFileArray = new File[results.size()];
        results.toArray(tocFileArray);
        return tocFileArray;
    }

    public void invoke(File directory, RpfTocCrawlerListener listener)
    {
        File validDir = this.validateDirectory(directory);
        this.process(validDir, listener, false);
    }

    public void start(File directory, RpfTocCrawlerListener listener)
    {
        this.threadLock.lock();
        try
        {
            if (this.thread != null || this.thread == deadThread)
            {
                String message = WorldWind.retrieveErrMsg("RpfTocCrawler.BadStart");
                WorldWind.logger().log(FINE, message);
                throw new IllegalStateException(message);
            }
            File validDir = this.validateDirectory(directory);
            this.thread = new Thread(new RpfTocRunner(this, validDir, listener));
            this.thread.start();
        }
        finally
        {
            this.threadLock.unlock();
        }
    }

    public void stop()
    {
        this.threadLock.lock();
        try
        {
            this.thread = deadThread;
        }
        finally
        {
            this.threadLock.unlock();
        }
    }

    private File validateDirectory(File directory)
    {
        if (directory == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull");
            WorldWind.logger().log(FINE, message);
            throw new IllegalArgumentException(message);
        }
        String path = directory.getAbsolutePath();
        if (!path.endsWith("/") && !path.endsWith("\\"))
        {
            path = path + File.separatorChar;
            directory = new File(path);
        }
        if (!directory.exists())
        {
            String message = WorldWind.retrieveErrMsg("generic.fileNotFound");
            WorldWind.logger().log(FINE, message);
            throw new IllegalArgumentException(message);
        }
        return directory;
    }
}
