package scg.web;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import scg.HTML;
import scg.Util;
import scg.exception.MissingArgumentException;
import scg.tournament.Session;
import scg.tournament.TournamentManager;
import scg.tournament.Users;
import edu.neu.ccs.demeterf.http.classes.HTTPReq;
import edu.neu.ccs.demeterf.http.classes.HTTPResp;

public abstract class WebPage {
  protected static final String SESSION = "session";
  protected final static String USER = "user";
  protected static final String REFRESH = "refresh";
  protected static final String TOURNAMENT_ID = "tournament_id";
  protected static final String RESOURCE_REQUEST = "resource";
  protected static final String CONFIG_RESOURCE_REQUEST = "config";
  protected static final String AVATAR_RESOURCE_REQUEST = "avatar";
  protected static final String RAW_HISTORY_RESOURCE_REQUEST = "raw_history";
  protected static final String SMART_HISTORY_RESOURCE_REQUEST = "smart_history";
  protected static final String HISTORY_RESOURCE_REQUEST_FILE = "history_file";
  protected static final int REFRESH_TIME = 30;
  protected static final String CSS = "\n div{ padding:10px;display: block; }"
      + "\n table,td{ border:blue 1px solid;padding:5px; }"
      + "\n td,th{ border-spacing:5px; }"
      + "\n td.num{ text-align:center; }"
      + "\n th{ font-style:italic;border:solid navy 1px; padding:5px;}"
      + "\n .error{ color:red;text-align:center;margin-left: auto;width: 400px;margin-right: auto; }"
      + "\n .odd{ background-color:#D0D0D0; }"
      + "\n .current{ background-color:#40D040; }"
      + "\n .kicked{ background-color:#E04040; }"
      + "\n .time{ font-size:12px; float: right; }"
      + "\n .sidestatus{ position:absolute; top:0px; right:0px;  }"
      + "\n .sidenav{ position:absolute; top:0px; left:0px; font-size:12px;padding:20px;  }"
      + "\n .title{ font-size:32px;font-weight:bold;text-align:center;margin-left: auto;margin-right: auto; }"
      + "\n .signin, .signup, .logout, .refresh{ text-align:center;margin-left: auto;width: 400px;margin-right: auto;}"
      + "\n .preform{ text-align:center;margin-left: auto;width: 400px;margin-right: auto;}"
      + "\n .tournaments{ text-align:center;margin-left: auto;margin-right: auto;}"
      + "\n .user{ font-size:12px; float:right; }"
      + "\n .config{ width:550px;height:120px; }"
      + "\n .newTournament, .editUser{ float:left; }"
      + "\n .approveUser{ left; }"
      + "\n .center{ text-align:center; }";

  protected final TournamentManager _manager;
  protected final HTTPReq _request;
  protected final Users _users;
  protected final Map<String, String> _urlArgs;
  protected final Map<String, String> _postArgs;
  protected final Map<String, String> _headers;
  protected final boolean _refresh;
  protected final String _trimmedURL;
  protected final Session _session; // Can be null
  protected final boolean _sessionExists;

  protected WebPage(TournamentManager manager, HTTPReq request, Users users) {
    _manager = manager;
    _request = request;
    _users = users;
    _headers = _request.getHeaders().toJavaMap();
    try {
      _urlArgs = splitArgsURL(_request.getHead().getUrl().toString());
      _postArgs = splitArgs(_request.getBody().toString());
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
    _refresh = _urlArgs.containsKey(REFRESH);
    _trimmedURL = _request.trimmedUrl();
    String sessionKey = _urlArgs.get(SESSION);
    if (sessionKey == null) {
      sessionKey = _postArgs.get(SESSION);
    }
    if (sessionKey == null) {
      _session = null;
    } else {
      _session = _users.getSession(sessionKey);
    }
    _sessionExists = _session != null;
  }

  private static Map<String, String> splitArgs(String s) throws UnsupportedEncodingException {
    Map<String, String> args = new HashMap<String, String>();
    if (s.contains("=")) {
      for (String piece : s.split("&")) {
        String[] kv = piece.split("=");
        if (kv.length > 0) {
          args.put(URLDecoder.decode(kv[0], "UTF-8"), (kv.length > 1) ? URLDecoder.decode(kv[1],
              "UTF-8") : "");
        }
      }
    }
    return args;
  }

  private static Map<String, String> splitArgsURL(String s) throws UnsupportedEncodingException {
    int question = s.indexOf('?');
    return splitArgs((question < 0) ? "" : s.substring(question + 1).trim());
  }

  public abstract HTTPResp getResponse();

  protected static String divWrap(String content, String cssClass) {
    return HTML.wrap(content, "div", cssClass);
  }

  protected String refreshToggleLink() {
    return refreshToggleLink(false);
  }

  protected String refreshToggleLink(boolean withArgs) {
    StringBuilder builder = new StringBuilder();
    builder.append("<a href=\"");
    builder.append(_trimmedURL);
    boolean needsAnd = false;
    if (!_refresh) {
      builder.append("?" + REFRESH + "=true");
      needsAnd = true;
    }
    if (withArgs) {
      boolean sessionAdded = false;
      for (Entry<String, String> e : _urlArgs.entrySet()) {
        if (!e.getKey().equals(REFRESH)) {
          builder.append((needsAnd ? "&" : "?"));
          builder.append(e.getKey() + "=" + e.getValue());
          needsAnd = true;
          if (e.getKey().equals(SESSION)) {
            sessionAdded = true;
          }
        }
      }
      if (!sessionAdded && _postArgs.containsKey(SESSION)) {
        builder.append((needsAnd ? "&" : "?") + SESSION + "=" + _session.getKey());
      }
    }
    builder.append("\">Refresh ");
    builder.append(_refresh ? "Off" : "On");
    builder.append("</a>");
    return builder.toString();
  }

  protected static void builderAppendLn(StringBuilder builder, String content) {
    builder.append(content);
    builder.append("\n");
  }

  protected void buildPageHead(StringBuilder builder, String title) {
    buildPageHead(builder, title, true);
  }

  protected void buildPageHead(StringBuilder builder, String title, boolean withStatus) {
    if (withStatus) {
      buildSideStatus(builder, _session);
      buildSideNav(builder, _session);
    }
    builderAppendLn(builder, divWrap(title, "title"));
  }

  protected void buildSideStatus(StringBuilder builder, Session s) {
    builderAppendLn(builder, "<div class=\"sidestatus\">");
    if (s != null && !s.hasExpired()) {
      builderAppendLn(builder, divWrap(s.getUser().getName() + " | "
          + link(redirectURL(SignIn.PATH) + "?" + UserPage.LOGOUT + "=true&" + SESSION + "="
              + s.getKey(), "Sign Out"), USER));
    }
    builderAppendLn(builder, divWrap(Util.printDate(Util.now()), "time"));
    builderAppendLn(builder, "</div>");
  }

  private void buildSideNav(StringBuilder builder, Session s) {
    if (s != null && !s.hasExpired()) {
      boolean needPipe = false;
      builderAppendLn(builder, "<div class=\"sidenav\">");
      if (s.getUser().isRoot() && !_trimmedURL.equals(AdminPage.PATH)) {
        builderAppendLn(builder, link(redirectURL(AdminPage.PATH) + "?" + SESSION + "="
            + s.getKey(), "Admin Page"));
        needPipe = true;
      }
      if (!_trimmedURL.equals(ServerStatus.PATH)) {
        if (needPipe) {
          builder.append(" | ");
        }
        builderAppendLn(builder, link(redirectURL(ServerStatus.PATH) + "?" + SESSION + "="
            + s.getKey(), "Server Status"));
      }
      builderAppendLn(builder, "</div>");
    }
  }

  protected void noSession(StringBuilder builder) {
    requiresSignIn(builder, "You must sign-in before you can access this page.");
  }

  protected void sessionExpired(StringBuilder builder) {
    requiresSignIn(builder, "Your session has expired. You must sign-in again.");
  }

  private void requiresSignIn(StringBuilder builder, String msg) {
    builderAppendLn(builder, divWrap(msg + "<br />" + link(redirectURL(SignIn.PATH), "Sign In"),
        "error"));
  }

  protected String redirectURL(String destinationPath) {
    return _trimmedURL.substring(0, _trimmedURL.lastIndexOf("/")) + destinationPath;
  }

  protected static String link(String url, String text) {
    return "<a href=\"" + url + "\">" + text + "</a>";
  }
  
  protected static String getArgument(Map<String, String> map, String key) throws MissingArgumentException {
    String valueString = map.get(key);
    if (valueString == null) {
      throw new MissingArgumentException(key);
    }
    return valueString;
  }
}