/* 9P Protocol JAVA
 * classFS/ramfs/node_type.java
 * Autor: Jaime Garzon (2005) jgarzon@gmail.com
 *
 * Esta clase representa un arbol de ficheros en memoria
 *    con sus permisos, fechas, datos...
 * Tiene metodos para poder manipular o leer el contenido
 *    del fichero.
*/

package PFS.ramfs;

import java.io.*;
import java.util.*;

public class node_type {

   //**************************************
   //PRIVATE variables
   //**************************************

   private boolean is_dir;
   private ByteArray data;
   private Vector dir;
   private String path;
   private node_type back_node;
   private int len;
   private long mode;
   private long mtime;
   private long atime;
   private Date clock=new Date();

   //**************************************
   //CONSTRUCTORS
   //**************************************

   //Constructor de creacion del nodo "root"
   public node_type(long nmode) {
      path="/";
      len=0;
      back_node=this;
      setmtime();
      mode=nmode;
      is_dir=true;
      dir=new Vector();
   }

   //Constructor de creacion de ficheros o directorios
   private node_type( boolean d, String name, node_type bnode, long nmode) {

      path=name;
      mode=nmode;
      back_node=bnode;
      setmtime();
      is_dir=d;

      if (is_dir) {
         dir=new Vector();
      }
      else {
         data = new ByteArray();
      }
   }

   //**************************************
   //PUBLIC methods
   //**************************************

   //INFO Methods
   //**************************************
   public String getName() {
      return path;
   }

   public long getSize() {
      return len;
   }

   public long getId() {
      return (long)this.hashCode();
   }

   public long getMode() {
      return mode;
   }

   public boolean isDir() {
      return is_dir;
   }

   public long getmtime() {
      return mtime;
   }

   public long getatime() {
      return atime;
   }

   public boolean exist_file(String name) {
      return (getIndexOfChild(name)!=-1);
   }

   public node_type[] listDir() throws Exception {
      node_type[] st;

      int tam;
      permToRead();
      if (!is_dir)
         throw new Exception("Object is file.");
      if (dir.isEmpty())
         return new node_type[0];

      tam=dir.size();
      st = new node_type[tam];
      for(int i=0; i<tam; i++)
         st[i] = (node_type) dir.elementAt(i);
      setatime();
      return st;
   }

   public String[] list() {
      String[] rt;
      int tam;

      if (!is_dir)
         return null;
      if (dir.isEmpty())
         return null;

      tam=dir.size();
      rt = new String[tam];
      for(int i=0; i<tam; i++)
          rt[i] = ((node_type) dir.elementAt(i)).getName();
      return rt;
   }

   public synchronized void setName(String s) { path=s; }
   public synchronized void setMode(long nmode) { mode=nmode & 0xFFFFFFFFL; }

   // TIME METHODS
   //**************************************
   public synchronized void setmtime(long t) {
      mtime=t;
      atime=mtime;
   }

   public synchronized void setmtime() {
      mtime=System.currentTimeMillis()/1000;
      atime=mtime;
   }

   public synchronized void setatime(long t) {
      atime=t;
   }

   public synchronized void setatime() {
      atime=System.currentTimeMillis()/1000;
   }

   // FILE CREATE
   //**************************************
   public void delete() throws Exception {
      node_type upnode=upNode();
      if (is_dir&&(dir.size()!=0))
         throw new Exception("Directory not empty.");
      permToWrite();

      node_type nodo_temp;
      int tam=upnode.dir.size();
      for(int i=0; i<tam; i++) {
         nodo_temp = (node_type) upnode.dir.elementAt(i);
         if (nodo_temp.getName()==getName()) {
            upnode.dir.removeElementAt(i);
            break;
         }
      }
   }

   public void mkdir(String name, long nmode) throws Exception {
      permToWrite();
      if (checkGoodName(name))
         throw new Exception("Bad file name.");
      if (getIndexOfChild(name)!=-1)
         throw new Exception("File name exist.");
      dir.addElement(new node_type(true,name,this,nmode));
   }

   public void create_file(String name, long nmode) throws Exception {
      permToWrite();
      if (checkGoodName(name))
         throw new Exception("Bad file name.");
      if (getIndexOfChild(name)!=-1)
         throw new Exception("File name exist.");
      dir.addElement(new node_type(false,name,this,nmode));
   }

   // WRITE & READ
   //**************************************
   public byte[] read(int num, int offset) throws Exception {
      permToRead();
      if (is_dir)
         throw new Exception("Cant read directory.");
      byte[] temp = data.readBytes(num, offset);
      setatime();
      return temp;
   }

   public int write(byte[] cad, int offset) throws Exception {
      permToWrite();
      if (is_dir)
         throw new Exception("Cant write directory.");
      int num_bytes=data.writeBytes(cad,offset);
      update_file_size();
      setmtime();
      return num_bytes;
   }

   public void truncate() throws Exception {
      permToWrite();
      if (is_dir)
         throw new Exception("Cant write directory.");
      data.delete();
      update_file_size();
      setmtime();
   }

   // CHANGE DIR
   //**************************************
   public node_type downNode(String name) throws Exception {
      int tam;
      node_type nodo_temp;
      if (name.equals(".."))
         return upNode();
      if (name.equals("."))
         return this;
      if (dir.isEmpty())
         throw new Exception("File not found.");

      tam=dir.size();
      for(int i=0; i<tam; i++) {
         permToExec();
         nodo_temp = (node_type) dir.elementAt(i);
         if (name.equals(nodo_temp.path)) {
            return nodo_temp;
         }
      }
      throw new Exception("File not found.");
   }

   public node_type upNode() throws Exception {
      int tam;
      node_type nodo_temp;
      if (back_node==null)
         throw new Exception("File is root.");
      return back_node;
   }

   //**************************************
   //PRIVATE methods
   //**************************************

   private void update_file_size() {
      len = data.size();
   }

   private int getIndexOfChild (String name) {
      int tam;
      node_type nodo_temp;
      if (dir.isEmpty())
         return -1;
      tam=dir.size();
      for(int i=0; i<tam; i++) {
         nodo_temp = (node_type) dir.elementAt(i);
         if (nodo_temp.path==name)
            return i;
      }
      return -1;
   }

   private String getNameOfChild (int index) {
      int tam;
      node_type nodo_temp;

      if (dir.isEmpty()||(dir.size()>=index))
         return ("");
      nodo_temp = (node_type) dir.elementAt(index);
      return(nodo_temp.path);
   }

   private boolean checkGoodName (String cad){
      if (cad.indexOf("?")==-1)
         return false;
      if (cad.indexOf("*")==-1)
         return false;
      if (cad.indexOf("/")==-1)
         return false;
      return true;
   }


   //****************************
   // Permission methods
   //****************************

   private static final int DMREAD   = 0x4;
   private static final int DMWRITE  = 0x2;
   private static final int DMEXEC   = 0x1;

   private void permToRead () throws Exception  {
      if (!isReadable())
         throw new Exception("Access permission denied [read]") ;
   }

   private void permToWrite () throws Exception  {
      if (!isWritable())
         throw new Exception("Access permission denied [write]") ;
   }

   private void permToExec () throws Exception  {
      if (!isExecutable())
         throw new Exception("Access permission denied [exec]") ;
   }

   private boolean checkPerm (int c) {
      int dm = c  << getPermGroup();
      return ((mode&dm)==dm);
   }

   public boolean isReadable() {
      return checkPerm(DMREAD);
   }

   public boolean isWritable() {
      return checkPerm(DMWRITE);
   }

   public boolean isExecutable() {
      return checkPerm(DMEXEC);
   }

   // FUNCIONES PARA FUTURAS IMPLEMENTACIONES DE GRUPOS DE USUARIOS.
   //**************************************
   private static boolean isUserOwner() {
      return true;
   }

   private static boolean isUserMember(String group) {
      return true;
   }

   private static byte getPermGroup() {
      //actualmente solo se mira el permiso "propietario",
      //se retornaria:  0 otros (bits 2,1,0)
      //                3 grupo (bits 5,4,3)
      //                6 propietario (bits 8,7,6)
      return 6;
   }
}