package bilab;

import java.net.URL;
import java.io.File;
import java.io.IOException;
import java.io.FilenameFilter;
import java.lang.annotation.Annotation;
import java.util.regex.*;


import scigol.Any;
import scigol.TypeSpec;
import scigol.Value;


public class Util
{
  @Summary("find and read in a file containing any type of supported data of the given type (which may be \"unknown\")")
  public static Any readResource(String resourceName, String resourceType)
  {
    try {
      ResourceManager rm = BilabPlugin.getResourceManager();
      URL url = rm.findResource(resourceName);
      
      String fullResourceName = url.toString();

      Object obj = null;
      // special case for HTML as java.net.URL doesn't implement IResourceIOProvider
      if (extension(fullResourceName).equals("html") || extension(fullResourceName).equals("htm")) 
        obj = url;
      else
        obj = rm.instantiateObjectFromResource(fullResourceName, resourceType);

      if (obj != null) {
        if (obj instanceof Any)
          return (Any)obj;
        else
          return new Any(obj);
      }
      
    } catch (IOException e) {
      throw new BilabException("unable to find or open resource "+resourceName+" - IO error "+e);
    } catch (BilabException e) {
      throw e;
    } catch (Exception e) {
      throw new BilabException("unable to read resource "+resourceName+" - "+e);
    }
    throw new BilabException("unrecognised or unsupported data format in resource "+resourceName);
  }
  
  
  // gets the resource/file-name extension, if present, or the empty string
  public static String extension(String resourceName)
  {
    int dotIndex = resourceName.lastIndexOf('.');
    if (dotIndex == -1) return "";
    
    String end = resourceName.substring(dotIndex+1, resourceName.length());
    
    if ((end.indexOf('/') == -1) && (end.indexOf('\\') == -1)) 
        return end; // return as extension if doesn't contain '/' or '\'
      
    return "";
  }
  
  
  // get the name part of the resource/file-name (the last component sans extension)
  public static String name(String resourceName)
  {
    String name = resourceName;
    int i = name.lastIndexOf('/');
    if (i!=-1)
      name = name.substring(i+1);
    i = name.lastIndexOf('.');
    if (i!=-1)
      name = name.substring(0,i);
    return name;
  }
  
  
  
/*  
  public static scigol.Matrix I(int d) 
  {
    
    scigol.Matrix m = new scigol.Matrix();
    for(int r=0; r<d; r++) {
      // construct row vector
      scigol.Vector z = new scigol.Vector();
      for(int c=0; c<d; c++)
        z.appendElement(new Integer((c==r)?1:0));

      // append it
      m.appendRowVector(z);
    }
    
    return m;
  }
  */
  
  
  // convert a path-like string containing '/' seperators to use the platform native separators
  public static String toNativePathSeparator(String path)
  {
    if (File.separatorChar == '/') return path;
    
    StringBuffer sb = new StringBuffer();
    for(int c=0; c<path.length();c++)
      if (path.charAt(c) != '/')
        sb.append(path.charAt(c));
      else
        sb.append('\\');
    
    String npath = sb.toString();
    
    // if starts with '\C:' (or any drive letter), remove the \
    if ((npath.length()>2) && (npath.charAt(0) == '\\') && (npath.charAt(2) == ':'))
      return npath.substring(1);
    
    return npath;
  }

  // convert a path-like string containing platform native seperators to use '/' separators
  public static String toForwardPathSeparator(String path)
  {
    if (File.separatorChar == '/') return path;
    
    StringBuffer sb = new StringBuffer();
    for(int c=0; c<path.length();c++)
      if (path.charAt(c) != '\\')
        sb.append(path.charAt(c));
      else
        sb.append('/');
    return sb.toString();
  }

  /*
  // encode a url-like string to make it a valid URL
  //  replaces invalid chars with %HH, spaces with '+' and leaves '/' unchanged in the path part
  public static String encodeURL(String urlString)
  {
// not sure how to best do this
    // need to encode anything between delimiters.  Delimiters are /, & ? etc.
    // see RFC2396
// Actually, I think the URI class can probably do everything I want
    // (specifically the long form constructor that takes the path
    //   seperately)
    
    
    if (urlString==null) return null;
    
    URL url = new URL(urlString);
    String scheme = url.
    
    StringBuffer sb = new StringBuffer(64);
    char c;
    for(int i=0; i<url.length(); i++) {
      c = url.charAt(i);
      
    }
  }
  
  
  public static String decodeURL(String url)
  {
    
  }
  */
  
  
  public static Double mean(scigol.Vector v)
  {
    int d = v.get_size();
    
    double t = 0;
    for(int i=0; i<d; i++) {
      t += (double)((Integer)v.get_Item(i).value).intValue();
    }
    return new Double(t/d);
  }
  
  
  public static scigol.Map resourceTypesMap()
  {
    return BilabPlugin.getResourceManager().resourceTypesMap();
  }
  
  
  
  public static scigol.Any help(String command)
  {
    StringBuilder sb = new StringBuilder();
    
    if (command.equals(".help")) {
 
      sb.append("Type '.show quickstart' for a quick-start guide\n");
      sb.append(".list                 - list functions and types in the current scope\n");
      sb.append(".ls [<pattern>] or .dir [<pattern>] - list files in current directory\n");
      sb.append(".cd <directory>       - change the current directory\n");
      sb.append(".pwd                  - print (show) current working directory\n");
      sb.append(".help [<func>|<type>] - get help on a specific function or type\n");
      sb.append(".typeof <value>       - shows the actual type of the given value\n");
      sb.append(".run <resource>       - executes a Scigol source resource (e.g. a file)\n");
      sb.append(".using <namespace>    - make definitions in <namespace>'s scope available in the current scope\n");
      sb.append(".show <value>         - show <value> in the value view frame (i.e. default is lower right frame)\n");
      return new scigol.Any(sb.toString());
      
    }
    else if (command.startsWith(".help ")) {
      String helpTarget = command.substring(6);

      scigol.NamespaceScope globalScope = BilabPlugin.getDefault().getGlobalScope();
      
      scigol.TypeSpec summaryAnnotationType = new scigol.TypeSpec(Summary.class);
      scigol.TypeSpec docAnnotationType = new scigol.TypeSpec(Doc.class);

      scigol.Entry entries[] = globalScope.getEntries(helpTarget,null);
      
      if (entries.length > 0) {

        scigol.Entry entry = entries[0]; // just take the first
        String name = entry.name;
        
        Annotation docAnnot = entry.getAnnotation(docAnnotationType);
        if (docAnnot != null) {

          String doc = null;
          if (docAnnot instanceof scigol.ScigolAnnotation) {
            scigol.Value v = (scigol.Value)((scigol.ScigolAnnotation)docAnnot).getMembers().get(0);
            doc = (String)v.getValue();
          }
          else
            doc = ((Doc)docAnnot).value();
        
          if (!doc.startsWith("file:") && !doc.startsWith("http:"))
            sb.append(name+":\n"+doc);
          else {
            // return a URL (will be displayed by the registered HTML viewer)

            String URLString = doc;
            try {
              URL url=null;
              if (URLString.startsWith("file:")) { // treat is as a resource
                url = BilabPlugin.getResourceManager().findResource(URLString.substring(5));
              }
              else
                url = new URL(URLString);
            
              Notify.devInfo(Util.class, "Help URL "+URLString);
            
              return new scigol.Any(url);
              
            } catch (java.net.MalformedURLException e) {
              sb.append(doc);
            } catch (IOException e) {
              throw new BilabException("error reading help resource "+URLString+" - "+e);
            }
            
          }
          
          return new scigol.Any(sb.toString());
        }
        else { // no Doc annotation, try for a Summary and use that instead
          String typeString = entry.type.toString();

          Annotation summaryAnnot = entry.getAnnotation(summaryAnnotationType);
          if (summaryAnnot != null) {
            String summary = null;
            if (summaryAnnot instanceof scigol.ScigolAnnotation) {
              scigol.Value v = (scigol.Value)((scigol.ScigolAnnotation)summaryAnnot).getMembers().get(0);
              summary = (String)v.getValue();
            }
            else
              summary = ((Summary)summaryAnnot).value();
            
            
            sb.append(name+": "+typeString+"\n"+summary);
            return new scigol.Any(sb.toString());
          }
          else { // no Summary either, at least report the name & type
            sb.append(name+": "+typeString+"\n");
            
          }
        }
        
      }
      
    }
    
    sb.append("no help available.\n");
    return new scigol.Any(sb.toString());
  }
  
  
  
  public static class RegexFilenameFilter implements FilenameFilter
  {
    public RegexFilenameFilter(String regex)
    {
      pattern = Pattern.compile(regex);
    }
    
    
    public boolean accept(File dir, String name) 
    {
      Matcher m = pattern.matcher(name);
      return m.matches();
    }
    
    
    private Pattern pattern;
  }
  
  
  
  
  private static File[] getFilteredFileList(String path, String arg)
  {
    boolean isPattern = arg.contains("?") || arg.contains("*"); 
    
    String pattern = ".*"; // match all by default
    if (isPattern) {
      // re-express unix shell style patterns as regex
      pattern = arg;
      
      // replace regex special chars with escaped versions
      pattern = pattern.replace("+","\\+");
      pattern = pattern.replace(".", "\\.");
      pattern = pattern.replace("^", "\\^");
      pattern = pattern.replace("$", "\\$");
      pattern = pattern.replace("(", "\\(");
      pattern = pattern.replace(")", "\\)");
      pattern = pattern.replace("[", "\\[");
      pattern = pattern.replace("{", "\\{");
      pattern = pattern.replace("|", "\\|");

      pattern = pattern.replace("*",".*");
      pattern = pattern.replace("?",".{1}");
      arg = "";
    }
    
    if (arg.length()>0) {
      if (arg.charAt(0)=='/') // abs path
        path = arg;
      else
        path += "/" + arg;
    }

    if ((path.length()==2) && (path.charAt(0)=='/') && Character.isLetter(path.charAt(1)))
      path += "/";
      
    String nativePath = toNativePathSeparator(path);
    // reinsert ':' if necessary
    if ((nativePath.length()>2) && (nativePath.charAt(0)=='\\') && (nativePath.charAt(2)=='\\')
        && Character.isLetter(nativePath.charAt(1)))
        nativePath = "\\"+nativePath.substring(1,2)+":\\"+nativePath.substring(3);
    
    File fpath = new File(nativePath);

    if (!fpath.isDirectory()) 
      return null;

    RegexFilenameFilter filter = new RegexFilenameFilter(pattern);
    
    return fpath.listFiles(filter);
  }
  
  
  
  public static String fileList(String path, String arg)
  {
    File[] files = getFilteredFileList(path,arg);
    
    if (files==null) 
      throw new BilabException("path is not a directory");
    
    if (arg.length()>0) {
      if (arg.charAt(0)=='/') // abs path
        path = arg;
      else
        path += "/" + arg;
    }

    boolean isPattern = arg.contains("?") || arg.contains("*"); 
    boolean showHidden = (isPattern && arg.equals(".*"));

    final int cols = 3;
    final int width = 20;
    
    int col = 0;
    StringBuilder sb = new StringBuilder();
    StringBuilder line = new StringBuilder();

    sb.append("Directory of "+path+"\n");

    for(File f : files) {
      boolean isHidden = f.isHidden() || (f.getName().charAt(0) == '.');
      if (!isHidden || showHidden) {
        line.append(f.getName());
        if (f.isDirectory()) line.append("/");
        col++;
        if (col==cols) {
          col=0;
          sb.append(line.toString()+"\n");
          line = new StringBuilder();
        }
        else {
          int pad = width - (line.length() % width);
          line.append(spaces.substring(0,pad));
        }
      }      
    }
    
    return sb.toString();
  }
  
  
  public static String changeDirectory(String path, String arg)
  {
    if (arg.length()==0) return path;
    
    if (arg.equals("..")) { // parent dir
      // check for '/', '\' and 'C:' 
      if (path.equals("/") || path.equals("\\") 
             || (path.length()==2 && path.charAt(1)==':' && Character.isLetter(path.charAt(0))) ) 
        return path;
      
      File pwd = new File(toNativePathSeparator(path));
      String newpath = toForwardPathSeparator( pwd.getParent() );
      newpath = newpath.replace(":","");
      if (newpath.charAt(0)!='/') newpath = "/"+newpath;
      
      return newpath;
    }
    else if (arg.equals("/") || arg.equals("\\")) // accept '/' or '\'
      return "/";
    else if (arg.length() == 2) { // accept '/C', '\C' or C:
      if ( ((arg.charAt(0)=='/') || (arg.charAt(0)=='\\')) && Character.isLetter(arg.charAt(1)) )
        return arg;
      if ( (arg.charAt(1)==':') && Character.isLetter(arg.charAt(0)) )
        return "/"+arg.charAt(0);
    }
    else if (arg.length() == 3) { // accept /C: or C:\
      if ((arg.charAt(0)=='/') && (arg.charAt(2)==':') && Character.isLetter(arg.charAt(1)))
        return "/"+arg.charAt(1);
      if (arg.endsWith(":\\") && Character.isLetter(arg.charAt(0)))
        return "/"+arg.charAt(0);
    }
    
    
    File[] files = getFilteredFileList(path,"");

    for(File f : files) {
      if (f.getName().equals(arg)) {
        String newpath = toForwardPathSeparator(f.getAbsolutePath());
        newpath = newpath.replace(":","");
        if (newpath.charAt(0)!='/') newpath = "/"+newpath;
        return newpath;
      }
    }
    
    throw new BilabException("directory not found");
  }
  
  
  
  
  // return a list of functions & classes in the current scope, along with doc summary
  public static String list()
  {
    scigol.NamespaceScope globalScope = BilabPlugin.getDefault().getGlobalScope();
    
    scigol.TypeSpec summaryAnnotationType = new scigol.TypeSpec(Summary.class);
    
    scigol.Entry entries[] = globalScope.getEntries(null,null);
   
    StringBuilder sb = new StringBuilder();
    
    for(scigol.Entry entry : entries) {
      
      Annotation summaryAnnot = entry.getAnnotation(summaryAnnotationType);
      if (summaryAnnot != null) {

        String summary = null;
        if (summaryAnnot instanceof scigol.ScigolAnnotation) {
          scigol.Value v = (scigol.Value)((scigol.ScigolAnnotation)summaryAnnot).getMembers().get(0);
          summary = (String)v.getValue();
        }
        else
          summary = ((Summary)summaryAnnot).value();
        
        sb.append( entry.name+" - "+summary+"\n" );
        
      }
      
    }
    
    return sb.toString();
  }
  
  
  // method to throw a BilabException (needed until scigol exceptions are implemented)
  public static void throwBilabException(String message)
  {
    throw new BilabException(message);
  }
  
  static final String spaces = "                                                                                               ";

  
  
}
