package commons.utl;
import java.io.File;
import java.util.Iterator;
import java.util.Properties;
import basics.application.Config;
import basics.application.user.User;
import basics.filesystem.file.FileUtl;
import basics.filesystem.file.LineWriter;
import basics.string.TaggedTokenReplacer;
import basics.unexpected.Failure;
import basics.unexpected.Problem;
import basics.utl.Empty;
import basics.utl.SUtl;
import basics.utl.SysUtl;
import commons.application.GLOBAL;
import commons.application.ctx.Context;
/**
 * Utilities for the {@link Context} object.
 * @author ks
 */
public class CtxUtl
{

public static void addPropertiesToEnv (Context ctx, Properties p)
{
   if (p == null) return;
   for (Iterator<Object> I = p.keySet().iterator(); I.hasNext();)
   {
      Object k = I.next();
      ctx.env().put((String) k, p.get(k));
   }
}

public static String loadTemplate (Context ctx, String name)
{
   String file = FileUtl.concat(userHome(ctx), name);
   return FileUtl.readBytes(file, "utf-8");
}

public static String resolveVariables (Context ctx, String input)
{
   TaggedTokenReplacer ttr = new TaggedTokenReplacer(input);
   try
   {
      for (Iterator<String> tokens = ttr.getAllTokens(); tokens.hasNext();)
      {
         String t = (String) tokens.next();
         Object obj = ctx.getr(t, (Object) null);
//         SysUtl.trace("Replace " + t + "=" + obj);
         ttr.replace(t, obj == null ? GLOBAL.EMPTYINPUT : obj.toString());
      }
   }
   catch (Problem e)
   {
      throw new Failure("Invalid expression (check occurrences of %): " + input);
   }
   return ttr.getText();
}

public static void resolveVariables (Context ctx)
{
   for (Iterator<String> keys = ctx.keyIterator(); keys.hasNext();)
   {
      String key = keys.next();
      Object val = ctx.getr(key);
      if (val instanceof String)
      {
         String vstr = val.toString();
         if (vstr.indexOf("%") > -1)
         {
            TaggedTokenReplacer ttr = new TaggedTokenReplacer(vstr);
            boolean replacements = false;
            try
            {
               for (Iterator<String> tokens = ttr.getAllTokens(); tokens.hasNext();)
               {
                  String t = (String) tokens.next();
                  Object obj = ctx.getr(t, (Object) null);
                  ttr.replace(t, obj == null ? GLOBAL.EMPTYINPUT : obj.toString());
                  replacements = !replacements ? true : replacements;
               }
            }
            catch (Problem e)
            {
               throw new Failure("Invalid expression, key(" + key + ") value(" + val + ")");
            }
            if (replacements)
            {
               // OsUtl.trace("replace " + key + " ->> " + ttr.getText());
               ctx.put(key, ttr.getText());
            }
            // else OsUtl.trace("*** No replacement: " + key);
         }
      }
      // else OsUtl.trace("*** No String: " + key);
   }
}

/**
 * Try to find a subctx by lookup. Use value assigned to key for lookup. Return fallback context if
 * no subctx available.
 */
public static Context lookupByKeyOrUse (String key, Context fallback)
{
   Context subctx = null;
   if (!Empty.is(key))
   {
      try
      {
         subctx = fallback.getChildCtxByLookup(key);
      }
      catch (Throwable t)
      {
         fallback.addError(t);
         return fallback;
      }
   }
   return (subctx == null) ? fallback : subctx;
}

/**
 * @param cfg - may stem from properties file, source.
 * @param ctx - sink, to be initialized with values from source. Copy all key/value pairs from
 * source to target, used to initialize a new Context during start. Note: Null-Values are not
 * copied, but are removed from sink!
 */
public static Context initCtx (Config cfg, Context ctx)
{
   if (cfg != null) for (Iterator<Object> iter = cfg.iterator(); iter.hasNext();)
      copyIfValue(cfg, ctx, (String) iter.next(), null);
   return ctx;
}

/**
 * @param cfg - source
 * @param ctx - sink
 * @param keySource - points to value in source
 * @param keyTarget - points to value in target, can be null, then takes keySource Note: if value is
 * null, the key will not be in the sink structure.
 */
public static void copyIfValue (Config cfg, Context ctx, String keySource, String keySink)
{
   if (cfg == null) return;
   Object value = cfg.get(keySource);
   keySink = keySink != null ? keySink : keySource;
   if (value != null) ctx.put(keySink, value);
   else ctx.remove(keySink);
}

public static void saveProfile (String path, Context ctx) throws Problem
{
   LineWriter writer = new LineWriter(path);
   if (!writer.open(false))
   {
      String msg = "Failed to open: " + path;
      throw new Problem(msg);
   }
   for (String key : ctx.keySet())
   {
      Object obj = ctx.getr(key);
      if (!(obj instanceof Context)) writer.write(key + "=" + ctx.getr(key, ""));
   }
   writer.close();
}

/**
 * return home folder, where all data is stored. home folder can be under user.home or in current
 * workfolder. the actual folder, however, is either . or the the system users name.
 */
public static String userHome (Context ctx)
{
   String basedirAlias = ctx.env().get(GLOBAL.CTX_ENVKEY_BASEDIR, GLOBAL.VAL_BASEDIR[0]);
   String basedir = basedirAlias.equalsIgnoreCase("home") ? SysUtl.getHomeDirectory() : SysUtl.getWorkingDirectory();
   String appdir = FileUtl.concat(basedir, ctx.env().get(GLOBAL.CTX_ENVKEY_APPDIR, GLOBAL.VAL_WORKFOLDER));
   User sysuser = (User) ctx.env().get(GLOBAL.CTX_ENVKEY_USER, (User) null);
   if (sysuser == null) throw new Failure("Unknown user.");
   if (Empty.is(sysuser.getId())) throw new Failure("Unknown user.");
   String userBasedir = sysuser.getId().equals(".") ? appdir : FileUtl.concat(appdir, sysuser.getId());
   ctx.env().put(GLOBAL.CTX_ENVKEY_BASEDIR, basedirAlias);
   ctx.env().put(GLOBAL.CTX_ENVKEY_USER, sysuser);
   return userBasedir;
}

/**
 * return home folder, where all data is stored that one used shares of his projects.
 */
public static String userShared (Context ctx)
{
   return FileUtl.concat(userHome(ctx), GLOBAL.VAL_SHAREDFOLDER);
}

/**
 * /** return main profile file-path. the profile is stored under home(), the name of this profile
 * is fix.
 */
public static String mainProfileFile (Context ctx)
{
   String userBasedir = userHome(ctx);
   String profile = FileUtl.concat(userBasedir, GLOBAL.CTX_VAL_MAINPROFILE + ".profile");
   return profile;
}

/**
 * return a subprofile filepath. all profiles are stored under home(), the name of the profile
 * depends on a setting in the context.
 */
public static String subProfileFile (Context ctx, String profilename)
{
   File userBasedir = scriptSpace(ctx);
   String profile = FileUtl.concat(userBasedir.getAbsolutePath(), profilename + ".profile");
   return profile;
}

public static void clearCmd (Context ctx)
{
   if (ctx != null)
   {
      ctx.env().remove(GLOBAL.CTX_ENVKEY_CMD);
      ctx.remove(GLOBAL.CTX_ENVKEY_ARG0);
      ctx.clearErrors();
   }
}

public static Context link (Context parent, String childname, Context child)
{
   child.setContextName(childname);
   child.setParent(parent);
   parent.put(childname, child);
   return child;
}

public static File inboxSpace (Context ctx)
{
   File inbox = new File(userHome(ctx), GLOBAL.VAL_SUBFOLDER_INBOX);
   File space = new File(inbox, ctx.getr(GLOBAL.CTX_KEY_INBOXSPACE, ""));
   return space;
}

public static File scriptSpace (Context ctx)
{
   File scripts = new File(userHome(ctx), GLOBAL.VAL_SUBFOLDER_SCRIPTS);
   return scripts;
}

public static File xmlSpace (Context ctx)
{
   File xml = new File(userHome(ctx), GLOBAL.VAL_SUBFOLDER_XDOCS);
   return xml;
}

public static File htmlSpace (Context ctx)
{
   File html = new File(userHome(ctx), GLOBAL.VAL_SUBFOLDER_HTMLDOCS);
   return html;
}

public static File storageHome (Context ctx)
{
   return new File(userHome(ctx), GLOBAL.VAL_SUBFOLDER_STORE);
}

public static File xmlStack (Context ctx)
{
   return new File(userHome(ctx), GLOBAL.VAL_SUBFOLDER_XDOCS_STACK);
}

public static double evalStringTerm (double inputValue, String term, Context ctx) throws Problem
{
   double result = inputValue;
   int op = 0;
   term = term.trim();
   if (term.startsWith("*")) op = 1;
   else if (term.startsWith("/")) op = 2;
   else if (term.startsWith("+")) op = 3;
   else if (term.startsWith("-")) op = 4;
   if (op == 0) throw new Problem("Unrecognized operation in " + term);
   String nes = (op < 3) ? "1" : "0";
   double ned = (op < 3) ? 1 : 0;
   term = term.substring(1).trim();
   TaggedTokenReplacer ttr = new TaggedTokenReplacer(term);
   // may only have ONE token or none:
   Iterator<String> iter = ttr.getAllTokens();
   if (iter.hasNext())
   {
      String token = iter.next();
      term = ttr.replace(token, ctx.getr(token, nes));
   }
   double value2 = SUtl.toDouble(term, ned);
   switch (op)
   {
   case 1:
      return inputValue * value2;
   case 2:
      return inputValue / value2;
   case 3:
      return inputValue + value2;
   case 4:
      return inputValue - value2;
   }
   return result;
}
}
