/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.sync.repositories.domain;

import java.util.ArrayList;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Logger;
import org.mozilla.gecko.sync.NonArrayJSONException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.repositories.android.RepoUtils;

import android.content.ContentValues;

/**
 * Represents a client's collection of tabs.
 *
 * @author rnewman
 *
 */
public class TabsRecord extends Record {

  // Immutable.
  public static class Tab {
    public final String    title;
    public final String    icon;
    public final JSONArray history;
    public final long      lastUsed;

    public Tab(String title, String icon, JSONArray history, long lastUsed) {
      this.title    = title;
      this.icon     = icon;
      this.history  = history;
      this.lastUsed = lastUsed;
    }

    public static Tab fromJSONObject(JSONObject o) throws NonArrayJSONException {
      ExtendedJSONObject obj = new ExtendedJSONObject(o);
      String title      = obj.getString("title");
      String icon       = obj.getString("icon");
      JSONArray history = obj.getArray("urlHistory");

      // Last used is inexplicably a string in seconds. Most of the time.
      long lastUsed = 0;
      Object lU = obj.get("lastUsed");
      if (lU instanceof Number) {
        lastUsed = ((Long) lU) * 1000L;
      } else if (lU instanceof String) {
        try {
          lastUsed = Long.parseLong((String) lU, 10) * 1000L;
        } catch (NumberFormatException e) {
          Logger.debug(LOG_TAG, "Invalid number format in lastUsed: " + lU);
        }
      }
      return new Tab(title, icon, history, lastUsed);
    }

    @SuppressWarnings("unchecked")
    public JSONObject toJSONObject() {
      JSONObject o = new JSONObject();
      o.put("title", title);
      o.put("icon", icon);
      o.put("urlHistory", history);
      o.put("lastUsed", this.lastUsed / 1000);
      return o;
    }

    public ContentValues toContentValues(String clientGUID, int position) {
      ContentValues out = new ContentValues();
      out.put(BrowserContract.Tabs.POSITION,    position);
      out.put(BrowserContract.Tabs.CLIENT_GUID, clientGUID);

      out.put(BrowserContract.Tabs.FAVICON,   this.icon);
      out.put(BrowserContract.Tabs.LAST_USED, this.lastUsed);
      out.put(BrowserContract.Tabs.TITLE,     this.title);
      out.put(BrowserContract.Tabs.URL,       (String) this.history.get(0));
      out.put(BrowserContract.Tabs.HISTORY,   this.history.toJSONString());
      return out;
    }

    @Override
    public boolean equals(Object o) {
      if (!(o instanceof Tab)) {
        return false;
      }
      final Tab other = (Tab) o;

      if (!RepoUtils.stringsEqual(this.title, other.title)) {
        return false;
      }
      if (!RepoUtils.stringsEqual(this.icon, other.icon)) {
        return false;
      }

      if (!(this.lastUsed == other.lastUsed)) {
        return false;
      }

      return Utils.sameArrays(this.history, other.history);
    }
  }

  private static final String LOG_TAG = "TabsRecord";

  public static final String COLLECTION_NAME = "tabs";
  public static final long TABS_TTL = 7 * 24 * 60 * 60; // 7 days in seconds.

  public TabsRecord(String guid, String collection, long lastModified, boolean deleted) {
    super(guid, collection, lastModified, deleted);
    this.ttl = TABS_TTL;
  }
  public TabsRecord(String guid, String collection, long lastModified) {
    this(guid, collection, lastModified, false);
  }
  public TabsRecord(String guid, String collection) {
    this(guid, collection, 0, false);
  }
  public TabsRecord(String guid) {
    this(guid, COLLECTION_NAME, 0, false);
  }
  public TabsRecord() {
    this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
  }

  public String clientName;
  public ArrayList<Tab> tabs;

  @Override
  public void initFromPayload(ExtendedJSONObject payload) {
    clientName = (String) payload.get("clientName");
    try {
      tabs = tabsFrom(payload.getArray("tabs"));
    } catch (NonArrayJSONException e) {
      // Oh well.
      tabs = new ArrayList<Tab>();
    }
  }

  @SuppressWarnings("unchecked")
  protected static JSONArray tabsToJSON(ArrayList<Tab> tabs) {
    JSONArray out = new JSONArray();
    for (Tab tab : tabs) {
      out.add(tab.toJSONObject());
    }
    return out;
  }

  protected static ArrayList<Tab> tabsFrom(JSONArray in) {
    ArrayList<Tab> tabs = new ArrayList<Tab>(in.size());
    for (Object o : in) {
      if (o instanceof JSONObject) {
        try {
          tabs.add(Tab.fromJSONObject((JSONObject) o));
        } catch (NonArrayJSONException e) {
          Logger.warn(LOG_TAG, "urlHistory is not an array for this tab.", e);
        }
      }
    }
    return tabs;
  }

  @Override
  public void populatePayload(ExtendedJSONObject payload) {
    putPayload(payload, "id", this.guid);
    putPayload(payload, "clientName", this.clientName);
    payload.put("tabs", tabsToJSON(this.tabs));
  }

  @Override
  public Record copyWithIDs(String guid, long androidID) {
    TabsRecord out = new TabsRecord(guid, this.collection, this.lastModified, this.deleted);
    out.androidID = androidID;
    out.sortIndex = this.sortIndex;
    out.ttl       = this.ttl;

    out.clientName = this.clientName;
    out.tabs = new ArrayList<Tab>(this.tabs);

    return out;
  }

  public ContentValues getClientsContentValues() {
    ContentValues cv = new ContentValues();
    cv.put(BrowserContract.Clients.GUID, this.guid);
    cv.put(BrowserContract.Clients.NAME, this.clientName);
    cv.put(BrowserContract.Clients.LAST_MODIFIED, this.lastModified);
    return cv;
  }

  public ContentValues[] getTabsContentValues() {
    int c = tabs.size();
    ContentValues[] out = new ContentValues[c];
    for (int i = 0; i < c; i++) {
      out[i] = tabs.get(i).toContentValues(this.guid, i);
    }
    return out;
  }
}
