/**
 * Copyright (c) 2007 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.ecore.resource.impl;


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;

import org.eclipse.emf.common.archive.ArchiveURLConnection;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.URIConverter;


public class ArchiveURIHandlerImpl extends URIHandlerImpl
{
  /**
   * Creates an instance.
   */
  public ArchiveURIHandlerImpl()
  {
    super();
  }

  @Override
  public boolean canHandle(URI uri)
  {
    return "archive".equals(uri.scheme());
  }

  @Override
  public OutputStream createOutputStream(URI uri, Map<?, ?> options) throws IOException
  {
    return createArchive(uri, options).getOutputStream();
  }

  @Override
  public InputStream createInputStream(URI uri, Map<?, ?> options) throws IOException
  {
    return createArchive(uri, options).getInputStream();
  }

  @Override
  public void delete(URI uri, Map<?, ?> options) throws IOException
  {
    createArchive(uri, options).delete();
  }

  @Override
  public boolean exists(URI uri, Map<?, ?> options)
  {
    try
    {
      InputStream inputStream = createInputStream(uri, options);
      inputStream.close();
      return true;
    }
    catch (IOException exception)
    {
      return false;
    }
  }

  @Override
  public Map<String, ?> getAttributes(URI uri, Map<?, ?> options)
  {
    return createArchive(uri, options).getAttributes();
  }

  @Override
  public void setAttributes(URI uri, Map<String, ?> attributes, Map<?, ?> options) throws IOException
  {
    createArchive(uri, options).setAttributes(attributes);
  }

  /**
   * A specialized class for reading from an archive.
   */
  protected class Archive extends ArchiveURLConnection
  {
    protected Map<?, ?> options;
    protected ZipEntry zipEntry;

    public Archive(URI uri, Map<?, ?> options)
    {
      super(uri.toString());
      this.options = options;
    }
    
    @Override
    protected boolean emulateArchiveScheme()
    {
      return false;
    }
    
    @Override
    protected boolean useZipFile()
    {
      return true;
    }
    
    @Override
    protected InputStream createInputStream(String nestedURL) throws IOException
    {
      return getURIConverter(options).createInputStream(URI.createURI(nestedURL), options);
    }
    
    @Override
    protected OutputStream createOutputStream(String nestedURL) throws IOException
    {
      return getURIConverter(options).createOutputStream(URI.createURI(nestedURL), options);
    }
    @Override
    protected InputStream yield(ZipEntry zipEntry, InputStream inputStream) throws IOException
    {
      this.zipEntry = zipEntry;
      Map<Object, Object> response = getResponse(options);
      if (response != null)
      {
        response.put(URIConverter.RESPONSE_TIME_STAMP_PROPERTY, zipEntry.getTime());
      }
      return super.yield(zipEntry, inputStream);
    }
    
    @Override
    protected OutputStream yield(ZipEntry zipEntry, OutputStream outputStream) throws IOException
    {
      this.zipEntry = zipEntry;
      Map<Object, Object> response = getResponse(options);
      if (response != null)
      {
        response.put(URIConverter.RESPONSE_TIME_STAMP_PROPERTY, zipEntry.getTime());
      }
      return super.yield(zipEntry, outputStream);
    }
    
    public Map<String, ?> getAttributes()
    {
      Map<String, Object> result = new HashMap<String, Object>();
      try
      {
        Set<String> requestedAttributes = getRequestedAttributes(options);
        if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_READ_ONLY))
        {
          Set<String> requestedSubAttributes = new HashSet<String>();
          requestedSubAttributes.add(URIConverter.ATTRIBUTE_READ_ONLY);
          Map<Object, Object> subOptions = new ExtensibleURIConverterImpl.OptionsMap(URIConverter.OPTION_REQUESTED_ATTRIBUTES, requestedSubAttributes, options);
          result.putAll(getURIConverter(subOptions).getAttributes(URI.createURI(getNestedURL()), subOptions));
        }

        InputStream inputStream = null;
        if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_DIRECTORY))
        {
          inputStream = getInputStream();
          inputStream.close();
          result.put(URIConverter.ATTRIBUTE_DIRECTORY, zipEntry.isDirectory());
        }
        if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_LENGTH))
        {
          if (inputStream == null)
          {
            inputStream = getInputStream();
            inputStream.close();
          }
          result.put(URIConverter.ATTRIBUTE_LENGTH, zipEntry.getSize());
        }
        if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_TIME_STAMP))
        {
          if (inputStream == null)
          {
            inputStream = getInputStream();
            inputStream.close();
          }
          result.put(URIConverter.ATTRIBUTE_TIME_STAMP, zipEntry.getTime());
        }
      }
      catch (IOException exception)
      {
        // Ignore exceptions.
      }
      return result;
    }

    public void setAttributes(Map<String, ?> attributes) throws IOException
    {
      Long timeStamp = (Long)attributes.get(URIConverter.ATTRIBUTE_TIME_STAMP);
      if (timeStamp != null)
      {
        setTimeStamp(timeStamp);
      }
    }
  }

  protected Archive createArchive(URI uri, Map<?, ?> options)
  {
    return new Archive(uri, options);
  }
}
