/*
 * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source 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 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.vfs;

import java.io.IOException;
import java.util.Map;

class BindPath extends FilesystemPath {
  private Node _node;
  private Path _backing;
  
  BindPath(Path backing)
  {
    this(null, "/", null, "/", null, backing);

    _root = this;
    if (backing instanceof FilesystemPath)
      _separatorChar = ((FilesystemPath) backing)._separatorChar;
  }

  /**
   * @param path canonical path
   */
  private BindPath(BindPath root,
		   String userPath, Map<String,Object> attributes,
		   String path, Node node, Path backing)
  {
    super(root, userPath, path);

    if (backing == null)
      throw new IllegalArgumentException("backing must not be null");

    if (node == null)
      node = new Node("", backing);

    if (backing == null)
      backing = node._backing;

    _node = node;
    _backing = backing;
    
    if (backing instanceof FilesystemPath)
      _separatorChar = ((FilesystemPath) backing)._separatorChar;
  }

  public Path fsWalk(String userPath,
			Map<String,Object> attributes,
			String path)
  {
    Node ptr = _node;

    int offset = 0;
    while (offset + 1 < path.length()) {
      if (ptr.firstChild == null)
	return ptr._backing.lookup(path.substring(offset), attributes);

      int p = path.indexOf(_separatorChar, offset + 1);
      String segment;
      if (p == -1)
	segment = path.substring(offset + 1);
      else
	segment = path.substring(offset + 1, p);

      Node next = ptr.findChild(segment);

      if (next == null)
	return ptr._backing.lookup(path.substring(offset), attributes);

      offset = p;
      ptr = next;
    }
    
    return new BindPath(this, userPath, attributes, path, _node, null);
  }

  public String getScheme()
  {
    return _root.getScheme();
  }

  public boolean exists()
  {
    return _backing.exists();
  }

  public boolean isDirectory()
  {
    return _backing.isDirectory();
  }

  public boolean isFile()
  {
    return _backing.isFile();
  }

  public long getLength()
  {
    return _backing.getLength();
  }

  public long getLastModified()
  {
    return _backing.getLastModified();
  }

  public boolean canRead()
  {
    return _backing.canRead();
  }

  public boolean canWrite()
  {
    return _backing.canWrite();
  }
  
  public String []list() throws IOException
  {
    String []list = _backing.list();
    
    if (_node.firstChild == null)
      return list;

    String []newList = new String[list.length + _node.size()];

    int i = 0;
    for (Node ptr = _node.firstChild; ptr != null; ptr = ptr.next)
      newList[i++] = ptr.name;

    for (int j = 0; j < list.length; j++)
      newList[i++] = list[j++];

    return newList;
  }
  
  public boolean mkdir()
    throws IOException
  {
    return _backing.mkdir();
  }
  
  public boolean mkdirs()
    throws IOException
  {
    return _backing.mkdirs();
  }
  
  public boolean remove()
    throws IOException
  {
    return _backing.remove();
  }
  
  public boolean renameTo(Path path)
    throws IOException
  {
    return _backing.renameTo(path);
  }

  public StreamImpl openReadImpl() throws IOException
  {
    return _backing.openReadImpl();
  }

  public StreamImpl openWriteImpl() throws IOException
  {
    return _backing.openWriteImpl();
  }

  public StreamImpl openReadWriteImpl() throws IOException
  {
    return _backing.openReadWriteImpl();
  }

  public StreamImpl openAppendImpl() throws IOException
  {
    return _backing.openAppendImpl();
  }

  void bind(String path, Path context)
  {
    Node ptr = _node;

    int offset = 0;
    while (offset + 1 < path.length()) {
      int p = path.indexOf(_separatorChar, offset + 1);
      String segment;
      if (p == -1)
	segment = path.substring(offset + 1);
      else
	segment = path.substring(offset + 1, p);

      Node next = ptr.findChild(segment);

      if (next == null)
	next = ptr.addChild(segment, ptr._backing);
    }

    ptr._backing = context;
  }

  public int hashCode()
  {
    return _backing.hashCode();
  }

  public boolean equals(Object b)
  {
    return _backing.equals(b);
  }

  public String toString()
  {
    return _backing.toString();
  }

  static class Node {
    Node parent;
    Node firstChild;
    Node next;

    String name;
    Path _backing;

    Node(String name, Path backing)
    {
      this.name = name;
      this._backing = backing;
    }

    int size()
    {
      int size = 0;

      for (Node ptr = firstChild; ptr != null; ptr = ptr.next)
	size++;

      return size;
    }

    Node findChild(String name)
    {
      for (Node ptr = firstChild; ptr != null; ptr = ptr.next) {
	if (ptr.name.equals(name))
	  return ptr;
      }

      return null;
    }

    Node addChild(String name, Path backing)
    {
      for (Node ptr = firstChild; ptr != null; ptr = ptr.next) {
	if (ptr.name.equals(name)) {
	  ptr._backing = backing;
	  return ptr;
	}
      }

      Node node = new Node(name, backing);
      node.next = firstChild;
      node.parent = this;
      firstChild = node;

      return node;
    }
  }
}

