/*
Copyright (c) 2011+, HL7, Inc
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this 
   list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
   this list of conditions and the following disclaimer in the documentation 
   and/or other materials provided with the distribution.
 * Neither the name of HL7 nor the names of its contributors may be used to 
   endorse or promote products derived from this software without specific 
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.

 */

package org.hl7.fhir.utilities;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import net.sf.saxon.TransformerFactoryImpl;

public class Utilities {

//	 private static final String TOKEN_REGEX = "^a-z[A-Za-z0-9]*$";


  /**
     * Returns the plural form of the word in the string.
     * 
     * Examples:
     * 
     * <pre>
     *   inflector.pluralize(&quot;post&quot;)               #=&gt; &quot;posts&quot;
     *   inflector.pluralize(&quot;octopus&quot;)            #=&gt; &quot;octopi&quot;
     *   inflector.pluralize(&quot;sheep&quot;)              #=&gt; &quot;sheep&quot;
     *   inflector.pluralize(&quot;words&quot;)              #=&gt; &quot;words&quot;
     *   inflector.pluralize(&quot;the blue mailman&quot;)   #=&gt; &quot;the blue mailmen&quot;
     *   inflector.pluralize(&quot;CamelOctopus&quot;)       #=&gt; &quot;CamelOctopi&quot;
     * </pre>
     * 
     * 
     * 
     * Note that if the {@link Object#toString()} is called on the supplied object, so this method works for non-strings, too.
     * 
     * 
     * @param word the word that is to be pluralized.
     * @return the pluralized form of the word, or the word itself if it could not be pluralized
     */
    public static String pluralizeMe( String word ) {
    	Inflector inf = new Inflector();
    	return inf.pluralize(word);
    }
    
  
  	public static boolean IsInteger(String string) {
  		try {
  			int i = Integer.parseInt(string);
  			return i != i+1;
  		} catch (Exception e) {
  			return false;
  		}
  	}
  	
  	public static boolean IsDecimal(String string) {
  		try {
  			float r = Float.parseFloat(string);
  			return r != r + 1; // just to suppress the hint
  		} catch (Exception e) {
  			return false;
  		}
  	}
  	
	public static String camelCase(String value) {
	  return new Inflector().camelCase(value.trim().replace(" ", "_"), false);
	}
	
	public static String escapeXml(String doco) {
		if (doco == null)
			return "";
		
		StringBuilder b = new StringBuilder();
		for (char c : doco.toCharArray()) {
		  if (c == '<')
			  b.append("&lt;");
		  else if (c == '>')
			  b.append("&gt;");
		  else if (c == '&')
			  b.append("&amp;");
      else if (c == '"')
        b.append("&quot;");
		  else 
			  b.append(c);
		}		
		return b.toString();
	}

	
	public static String capitalize(String s)
	{
		if( s == null ) return null;
		if( s.length() == 0 ) return s;
		if( s.length() == 1 ) return s.toUpperCase();
		
		return s.substring(0, 1).toUpperCase() + s.substring(1);
	}
	
  public static void copyDirectory(String sourceFolder, String destFolder, FileNotifier notifier) throws Exception {
    CSFile src = new CSFile(sourceFolder);
    if (!src.exists())
      throw new Exception("Folder " +sourceFolder+" not found");
    createDirectory(destFolder);
    
   String[] files = src.list();
   for (String f : files) {
     if (new CSFile(sourceFolder+File.separator+f).isDirectory()) {
       if (!f.startsWith(".")) // ignore .svn...
         copyDirectory(sourceFolder+File.separator+f, destFolder+File.separator+f, notifier);
     } else {
       if (notifier != null)
         notifier.copyFile(sourceFolder+File.separator+f, destFolder+File.separator+f);
       copyFile(new CSFile(sourceFolder+File.separator+f), new CSFile(destFolder+File.separator+f));
     }
   }
  }
	
  public static void copyFile(String source, String dest) throws IOException {
    copyFile(new File(source), new File(dest));
  }

	public static void copyFile(File sourceFile, File destFile) throws IOException {
		if(!destFile.exists()) {
			if (!new CSFile(destFile.getParent()).exists()) {
				createDirectory(destFile.getParent());
			}
			destFile.createNewFile();
		}

		FileChannel source = null;
		FileChannel destination = null;

		try {
			source = new FileInputStream(sourceFile).getChannel();
			destination = new FileOutputStream(destFile).getChannel();
			destination.transferFrom(source, 0, source.size());
		}
		finally {
			if(source != null) {
				source.close();
			}
			if(destination != null) {
				destination.close();
			}
		}
	}

  public static boolean checkFolder(String dir, List<String> errors)
  	throws IOException
  {
	  if (!new CSFile(dir).exists()) {
      errors.add("Unable to find directory "+dir);
      return false;
    } else {
      return true;
    }
  }

  public static boolean checkFile(String purpose, String dir, String file, List<String> errors) 
  	throws IOException
  {
    if (!new CSFile(dir+file).exists()) {
      errors.add("Unable to find "+purpose+" file "+file+" in "+dir);
      return false;
    } else {
      return true;
    }
  }

  public static String asCSV(List<String> strings) {
    StringBuilder s = new StringBuilder();
    boolean first = true;
    for (String n : strings) {
      if (!first)
        s.append(",");
      s.append(n);
      first = false;
    }
    return s.toString();
  }

  public static String asHtmlBr(String prefix, List<String> strings) {
    StringBuilder s = new StringBuilder();
    boolean first = true;
    for (String n : strings) {
      if (!first)
        s.append("<br/>");
      s.append(prefix);
      s.append(n);
      first = false;
    }
    return s.toString();
  }

  public static void clearDirectory(String folder) throws IOException {
	  String[] files = new CSFile(folder).list();
	  if (files != null) {
		  for (String f : files) {
			  File fh = new CSFile(folder+File.separatorChar+f);
			  if (fh.isDirectory()) 
				  clearDirectory(fh.getAbsolutePath());
			  fh.delete();
		  }
	  }
  }

  public static void createDirectory(String path) throws IOException{
    new CSFile(path).mkdirs();    
  }

  public static String changeFileExt(String name, String ext) {
    if (name.lastIndexOf('.') > -1)
      return name.substring(0, name.lastIndexOf('.')) + ext;
    else
      return name+ext;
  }
  
  public static String cleanupTextString( String contents )
  {
	  if( contents == null || contents.trim().equals("") )
		  return null;
	  else
		  return contents.trim();
  }


  public static boolean noString(String v) {
    return v == null || v.equals("");
  }


  public static byte[] saxonTransform(Map<String, byte[]> files, byte[] source, byte[] xslt) throws Exception {
    TransformerFactory f = new net.sf.saxon.TransformerFactoryImpl();
    f.setAttribute("http://saxon.sf.net/feature/version-warning", Boolean.FALSE);
    StreamSource xsrc = new StreamSource(new ByteArrayInputStream(xslt));
    f.setURIResolver(new ZipURIResolver(files));
    Transformer t = f.newTransformer(xsrc);
 
    t.setURIResolver(new ZipURIResolver(files));
    StreamSource src = new StreamSource(new ByteArrayInputStream(source));
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    StreamResult res = new StreamResult(out);
    t.transform(src, res);
    return out.toByteArray();    
  }
  
  public static byte[] transform(Map<String, byte[]> files, byte[] source, byte[] xslt) throws Exception {
    TransformerFactory f = TransformerFactory.newInstance();
    f.setAttribute("http://saxon.sf.net/feature/version-warning", Boolean.FALSE);
    StreamSource xsrc = new StreamSource(new ByteArrayInputStream(xslt));
    f.setURIResolver(new ZipURIResolver(files));
    Transformer t = f.newTransformer(xsrc);

    t.setURIResolver(new ZipURIResolver(files));
    StreamSource src = new StreamSource(new ByteArrayInputStream(source));
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    StreamResult res = new StreamResult(out);
    t.transform(src, res);
    return out.toByteArray();    
  }
  
  public static void bytesToFile(byte[] content, String filename) throws Exception {
    FileOutputStream out = new FileOutputStream(filename);
    out.write(content);
    out.close();
    
  }

  public static void saxonTransform(String xsltDir, String source, String xslt, String dest, URIResolver alt) throws Exception {
  	saxonTransform(xsltDir, source, xslt, dest, alt, null);
  }

  public static void saxonTransform(String xsltDir, String source, String xslt, String dest, URIResolver alt, Map<String, String> params) throws Exception {
    TransformerFactoryImpl f = new net.sf.saxon.TransformerFactoryImpl();
    f.setAttribute("http://saxon.sf.net/feature/version-warning", Boolean.FALSE);
    StreamSource xsrc = new StreamSource(new FileInputStream(xslt));
    f.setURIResolver(new MyURIResolver(xsltDir, alt));
    Transformer t = f.newTransformer(xsrc);
 		if (params != null) {
 			for (Map.Entry<String, String> entry : params.entrySet()) {
 				t.setParameter(entry.getKey(), entry.getValue());
 			}
  	}
    
    t.setURIResolver(new MyURIResolver(xsltDir, alt));
    StreamSource src = new StreamSource(new FileInputStream(source));
    StreamResult res = new StreamResult(new FileOutputStream(dest));
    t.transform(src, res);    
  }
  
  public static void transform(String xsltDir, String source, String xslt, String dest, URIResolver alt) throws Exception {

    TransformerFactory f = TransformerFactory.newInstance();
    StreamSource xsrc = new StreamSource(new FileInputStream(xslt));
    f.setURIResolver(new MyURIResolver(xsltDir, alt));
    Transformer t = f.newTransformer(xsrc);

    t.setURIResolver(new MyURIResolver(xsltDir, alt));
    StreamSource src = new StreamSource(new FileInputStream(source));
    StreamResult res = new StreamResult(new FileOutputStream(dest));
    t.transform(src, res);
    
  }


  public static String appendSlash(String definitions) {
    return definitions.endsWith(File.separator) ? definitions : definitions+File.separator;
  }


  public static String fileTitle(String file) {
    if (file == null)
      return null;
    String s = new File(file).getName();
    return s.indexOf(".") == -1? s : s.substring(0, s.indexOf("."));
  }


  public static String systemEol()
  {
	  return System.getProperty("line.separator");
  }

  public static String normaliseEolns(String value) {
    return value.replace("\r\n", "\r").replace("\n", "\r").replace("\r", "\r\n");
  }


  public static String unescapeXml(String xml) throws Exception {
    if (xml == null)
      return null;
    
    StringBuilder b = new StringBuilder();
    int i = 0;
    while (i < xml.length()) {
      if (xml.charAt(i) == '&') {
        StringBuilder e = new StringBuilder();
        i++;
        while (xml.charAt(i) != ';') {
          e.append(xml.charAt(i));
          i++;
        }
        if (e.toString().equals("lt")) 
          b.append("<");
        else if (e.toString().equals("gt")) 
          b.append(">");
        else if (e.toString().equals("amp")) 
          b.append("&");
        else if (e.toString().equals("quot")) 
          b.append("\"");
        else if (e.toString().equals("mu"))
          b.append((char)956);          
        else
          throw new Exception("unknown XML entity \""+e.toString()+"\"");
      }  else
        b.append(xml.charAt(i));
      i++;
    }   
    return b.toString();
  }


  public static boolean isPlural(String word) {
    word = word.toLowerCase();
    if ("restricts".equals(word) || "contains".equals(word) || "data".equals(word) || "specimen".equals(word))
      return false;
    Inflector inf = new Inflector();
    return !inf.singularize(word).equals(word);
  }


  public static String padLeft(String src, char c, int len) {
    StringBuilder s = new StringBuilder();
    for (int i = 0; i < len - src.length(); i++)
      s.append(c);
    s.append(src);
    return s.toString();
    
  }


  public static String path(String... args) {
    StringBuilder s = new StringBuilder();
    boolean d = false;
    for(String arg: args) {
      if (!d)
        d = true;
      else if (!s.toString().endsWith(File.separator))
        s.append(File.separator);
      s.append(arg);
    }
    return s.toString();
  }



//  public static void checkCase(String filename) {
//    File f = new CSFile(filename);
//    if (!f.getName().equals(filename))
//      throw new Exception("Filename  ")
//    
//  }

  public static String nmtokenize(String cs) {
    StringBuilder s = new StringBuilder();
    for (int i = 0; i < cs.length(); i++) {
      char c = cs.charAt(i);
      if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_')
        s.append(c);
      else if (c != ' ')
        s.append("."+Integer.toString(c));
    }
    return s.toString();
  }


  public static boolean isToken(String tail) {
    if (tail == null || tail.length() == 0)
      return false;
    boolean result = isAlphabetic(tail.charAt(0));
    for (int i = 1; i < tail.length(); i++) {
    	result = result && (isAlphabetic(tail.charAt(i)) || isDigit(tail.charAt(i)) || (tail.charAt(i) == '_')  || (tail.charAt(i) == '[') || (tail.charAt(i) == ']'));
    }
    return result;
  }


  private static boolean isDigit(char c) {
    return (c >= '0') && (c <= '9');
  }


  private static boolean isAlphabetic(char c) {
    return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
  }


  public static String getDirectoryForFile(String filepath) {
    int i = filepath.lastIndexOf(File.separator);
    if (i == -1)
      return filepath;
    else
      return filepath.substring(0, i);
  }

  public static String appendPeriod(String s) {
    if (Utilities.noString(s))
      return s;
    if (s.endsWith("."))
      return s;
    return s.trim()+".";
  }


  public static String removePeriod(String s) {
    if (Utilities.noString(s))
      return s;
    if (s.endsWith("."))
      return s.substring(0, s.length()-1);
    return s;
  }


	public static String stripBOM(String string) {
	  return string.replace("\uFEFF", "");
  }


  public static String oidTail(String id) {
    if (id == null || !id.contains("."))
      return id;
    return id.substring(id.lastIndexOf(".")+1);
  }


  public static String escapeJava(String doco) {
    if (doco == null)
      return "";
    
    StringBuilder b = new StringBuilder();
    for (char c : doco.toCharArray()) {
      if (c == '\r')
        b.append("\\r");
      else if (c == '\n')
        b.append("\\n");
      else if (c == '"')
        b.append("'");
      else 
        b.append(c);
    }   
    return b.toString();
  }


  public static String[] splitByCamelCase(String name) {
    List<String> parts = new ArrayList<String>();
    StringBuilder b = new StringBuilder();
    for (int i = 0; i < name.length(); i++) {
      if (i > 0 && Character.isUpperCase(name.charAt(i))) {
        parts.add(b.toString());
        b = new StringBuilder();
      }
      b.append(Character.toLowerCase(name.charAt(i)));
    }
    parts.add(b.toString());
    return parts.toArray(new String[] {} );
  }


  public static String encodeUri(String v) {
    return v.replace(" ", "%20");
  }



	public static String normalize(String s) {
		if (noString(s))
			return null;
	  StringBuilder b = new StringBuilder();
	  boolean isWhitespace = false;
	  for (int i = 0; i < s.length(); i++) {
	  	char c = s.charAt(i);
	  	if (!Character.isWhitespace(c)) { 
	  		b.append(Character.toLowerCase(c));
	  		isWhitespace = false;
	  	} else if (!isWhitespace) {
	  		b.append(' ');
	  		isWhitespace = true;	  		
	  	} 
	  }
	  return b.toString().trim();
  }


  public static void copyFileToDirectory(File source, File destDir) throws IOException {
  	copyFile(source, new File(path(destDir.getAbsolutePath(), source.getName())));
  }


	public static boolean isWhitespace(String s) {
	  boolean ok = true;
	  for (int i = 0; i < s.length(); i++)
	  	ok = ok && Character.isWhitespace(s.charAt(i));
	  return ok;
	  
  }


  public static String URLEncode(String string) {
    try {
      return URLEncoder.encode(string, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new Error(e.getMessage());
    }
  }


  public static boolean existsInList(String value, String... array) {
    for (String s : array)
      if (value.equals(s))
          return true;
    return false;
  }


  public static String getFileNameForName(String name) {
    return name.toLowerCase();
  }

  public static void deleteTempFiles() throws IOException {
    File file = createTempFile("test", "test");
    String folder = getDirectoryForFile(file.getAbsolutePath());
    String[] list = new File(folder).list(new FilenameFilter() {
      public boolean accept(File dir, String name) {
        return name.startsWith("ohfu-");
      }
    });
    if (list != null) {
      for (String n : list) {
        new File(path(folder, n)).delete();
      }
    }
  }

  public static File createTempFile(String prefix, String suffix) throws IOException {
 // this allows use to eaily identify all our dtemp files and delete them, since delete on Exit doesn't really work.
    File file = File.createTempFile("ohfu-"+prefix, suffix);  
    file.deleteOnExit();
    return file;
  }


	public static boolean isAsciiChar(char ch) {
		return ch >= ' ' && ch <= '~'; 
  }


  public static String makeUuidUrn() {
    return "urn:uuid:"+UUID.randomUUID().toString().toLowerCase();
  }

  public static boolean isURL(String s) {
    boolean ok = s.matches("^http(s{0,1})://[a-zA-Z0-9_/\\-\\.]+\\.([A-Za-z/]{2,5})[a-zA-Z0-9_/\\&\\?\\=\\-\\.\\~\\%]*");
    return ok;
 }

}
