/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.jena.riot.system.stream;

import java.io.File ;
import java.io.IOException ;
import java.io.InputStream ;
import java.security.AccessControlException ;

import org.apache.jena.atlas.io.IO ;
import org.apache.jena.atlas.lib.IRILib ;
import org.apache.jena.atlas.web.ContentType ;
import org.apache.jena.atlas.web.TypedInputStream ;
import org.apache.jena.riot.RDFLanguages ;
import org.apache.jena.util.FileUtils ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;

/** Location files in the filing system.
 *  A FileLocator can have a "current directory" - this is separate from any
 *  location mapping (see @link{LocationMapping}) as it applies only to files.
 */

public class LocatorFile implements Locator
{
    // Implementation note:
    // Java7: Path.resolve may provide an answer from the intricies of MS Windows
    
    static Logger log = LoggerFactory.getLogger(LocatorFile.class) ;
    private final String thisDir ;
    private final String thisDirLogStr ;

    /** Create a LocatorFile without a specific working directory.
     * Relative file names are relative to the working directory of the JVM.
     */
    public LocatorFile() { this(null) ; }
    
    /** Create a LocatorFile that uses the argument as it's working directory.
     * <p>
     * The working directory should be a UNIX style file name,
     * (relative or absolute), not a URI.
     * <p>
     * For MS Window, if asked to {@link #open} a file name with a drive letter,
     * the code assumes it is not relative to the working directory
     * of this {@code LocatorFile}.  
     */
    public LocatorFile(String dir)
    {
        if ( dir != null )
        {
            if ( dir.endsWith("/") || dir.endsWith(java.io.File.separator) )
                dir = dir.substring(0,dir.length()-1) ;
            thisDirLogStr = " ["+dir+"]" ;
        }
        else
            thisDirLogStr = "" ;
        thisDir = dir ;
    }

    /** Processing the filename for file: or relative filename
     *  and return a filename suitable for file operations. 
     */
    public String toFileName(String filenameIRI)
    {
        // Do not use directly : it will ignore the directory. 
        //IRILib.filenameToIRI
        
        String scheme = FileUtils.getScheme(filenameIRI) ;
        String fn = filenameIRI ;
        // Windows : C:\\ is not a scheme name!
        if ( scheme != null ) 
        {
            if ( scheme.length() == 1 )
            {
                // Not perfect for MS Windows but if thisDir is set then
                // the main use case is resolving relative (no drive)
                // filenames against thisDir. Treat the presence of a
                // drive letter as making this a JVM relative filename. 
                return fn ;
            }
            else if ( scheme.length() > 1 )
            {
                if ( ! scheme.equalsIgnoreCase("file") )
                    // Not file: IRI
                    return null ;
                fn = IRILib.IRIToFilename(filenameIRI) ;
                // fall through
            } 
        }
        // fn is the file name to use.
        return absolute(fn) ;
    }

    /** Make a filename (no URI scheme, no windows drive) absolute if there is
     * a setting for directory name thisDir  
     */
    private String absolute(String fn)
    {
        if ( thisDir != null && ! fn.startsWith("/") && ! fn.startsWith(File.separator) )
            fn = thisDir+File.separator+fn ;
        return fn ;
    }
    
    public String getThisDir()
    {
        return thisDir ;
    }

    public boolean hasCurrentDir()
    {
        return thisDir != null ;
    }


    public boolean exists(String fileIRI)
    {
        String fn = toFileName(fileIRI) ;
        if ( fn == null )
            return false ;
        
        return exists$(fn) ;
    }
    
    private boolean exists$(String fn)
    {
        if ( fn.equals("-") )
            return true ;
        return new File(fn).exists() ;
    }

    /** Open anything that looks a bit like a file name */ 
    @Override
    public TypedInputStream open(String filenameIRI)
    {
        String fn = toFileName(filenameIRI) ;
        if ( fn == null )
            return null ;
        
        try {
            if ( ! exists$(fn) )
            {
                if ( StreamManager.logAllLookups && log.isTraceEnabled())
                    log.trace("Not found: "+filenameIRI+thisDirLogStr) ;
                return null ;
            }
        } catch (AccessControlException e) {
            log.warn("Security problem testing for file", e);
            return null;
        }
        
        try {
            InputStream in = IO.openFileEx(fn) ;

            if ( StreamManager.logAllLookups && log.isTraceEnabled() )
                log.trace("Found: "+filenameIRI+thisDirLogStr) ;
            
            ContentType ct = RDFLanguages.guessContentType(filenameIRI) ;
            return new TypedInputStream(in, ct, filenameIRI) ;
        } catch (IOException ioEx)
        {
            // Includes FileNotFoundException
            // We already tested whether the file exists or not.
            log.warn("File unreadable (but exists): "+fn+" Exception: "+ioEx.getMessage()) ;
            return null ;
        }
    }
    
    @Override
    public String getName()
    {
        String tmp = "LocatorFile" ;
        if ( thisDir != null )
            tmp = tmp+"("+thisDir+")" ;
        return tmp ;
    }

    @Override
    public int hashCode()
    {
        final int prime = 31 ;
        int result = 1 ;
        result = prime * result + ((thisDir == null) ? 0 : thisDir.hashCode()) ;
        result = prime * result + ((thisDirLogStr == null) ? 0 : thisDirLogStr.hashCode()) ;
        return result ;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (this == obj) return true ;
        if (obj == null) return false ;
        if (getClass() != obj.getClass()) return false ;
        LocatorFile other = (LocatorFile)obj ;
        if (thisDir == null)
        {
            if (other.thisDir != null) return false ;
        } else
            if (!thisDir.equals(other.thisDir)) return false ;
        if (thisDirLogStr == null)
        {
            if (other.thisDirLogStr != null) return false ;
        } else
            if (!thisDirLogStr.equals(other.thisDirLogStr)) return false ;
        return true ;
    }
}
