package org.solovyev.android.calculator.history;

import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import org.json.JSONException;
import org.json.JSONObject;
import org.solovyev.android.Check;
import org.solovyev.android.calculator.DisplayState;
import org.solovyev.android.calculator.EditorState;
import org.solovyev.android.calculator.json.Json;
import org.solovyev.android.calculator.json.Jsonable;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class HistoryState implements Parcelable, Jsonable {

    public static final Creator<HistoryState> CREATOR = new Creator<HistoryState>() {
        @Override
        public HistoryState createFromParcel(Parcel in) {
            return new HistoryState(in);
        }

        @Override
        public HistoryState[] newArray(int size) {
            return new HistoryState[size];
        }
    };
    public static final Json.Creator<HistoryState> JSON_CREATOR = new Json.Creator<HistoryState>() {
        @NonNull
        @Override
        public HistoryState create(@NonNull JSONObject json) throws JSONException {
            return new HistoryState(json);
        }
    };
    private static final String JSON_EDITOR = "e";
    private static final String JSON_DISPLAY = "d";
    private static final String JSON_TIME = "t";
    private static final String JSON_COMMENT = "c";
    public final int id;
    @Nonnull
    public final EditorState editor;
    @Nonnull
    public final DisplayState display;
    protected long time = now();
    @Nonnull
    protected String comment = "";

    private HistoryState(@Nonnull EditorState editor, @Nonnull DisplayState display) {
        this.id = System.identityHashCode(this);
        this.editor = editor;
        this.display = display;
    }

    private HistoryState(@Nonnull HistoryState state, boolean newState) {
        this.id = newState ? System.identityHashCode(this) : state.id;
        this.editor = state.editor;
        this.display = state.display;
        this.time = state.time;
        this.comment = state.comment;
    }

    private HistoryState(@Nonnull JSONObject json) throws JSONException {
        this(EditorState.create(json.getJSONObject(JSON_EDITOR)), DisplayState.create(json.getJSONObject(JSON_DISPLAY)));
        this.time = json.optLong(JSON_TIME, 0L);
        this.comment = json.optString(JSON_COMMENT, "");
    }

    private HistoryState(Parcel in) {
        id = in.readInt();
        editor = in.readParcelable(EditorState.class.getClassLoader());
        display = in.readParcelable(DisplayState.class.getClassLoader());
        time = in.readLong();
        comment = in.readString();
    }

    @Nonnull
    public static Builder builder(@Nonnull EditorState editor, @Nonnull DisplayState display) {
        return new Builder(editor, display);
    }

    @Nonnull
    public static Builder builder(@Nonnull HistoryState state, boolean newState) {
        return new Builder(state, newState);
    }

    @Nonnull
    public static HistoryState create(@Nonnull JSONObject json) throws JSONException {
        return new HistoryState(json);
    }

    private static long now() {
        return System.currentTimeMillis();
    }

    @Nonnull
    public JSONObject toJson() throws JSONException {
        final JSONObject json = new JSONObject();
        json.put(JSON_EDITOR, editor.toJson());
        json.put(JSON_DISPLAY, display.toJson());
        json.put(JSON_TIME, time);
        if (!TextUtils.isEmpty(comment)) {
            json.put(JSON_COMMENT, comment);
        }
        return json;
    }

    @Nonnull
    public EditorState getEditor() {
        return editor;
    }

    @Nonnull
    public DisplayState getDisplay() {
        return display;
    }

    public long getTime() {
        return time;
    }

    @Nonnull
    public String getComment() {
        return comment;
    }

    public boolean same(@Nonnull HistoryState that) {
        return this.editor.same(that.editor) && this.display.same(that.display);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof HistoryState)) return false;

        final HistoryState that = (HistoryState) o;

        return id == that.id;

    }

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public String toString() {
        return "HistoryState{" +
                "id=" + id +
                ", editor=" + editor +
                ", display=" + display +
                ", time=" + time +
                ", comment='" + comment + '\'' +
                '}';
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeParcelable(editor, flags);
        dest.writeParcelable(display, flags);
        dest.writeLong(time);
        dest.writeString(comment);
    }

    public boolean isEmpty() {
        return display.isEmpty() && editor.isEmpty() && TextUtils.isEmpty(comment);
    }

    public static final class Builder {

        @NonNull
        private final HistoryState state;
        private boolean built;

        private Builder(@Nonnull EditorState editor, @Nonnull DisplayState display) {
            this.state = new HistoryState(editor, display);
        }

        private Builder(@Nonnull HistoryState state, boolean newState) {
            this.state = new HistoryState(state, newState);
            if (newState) {
                withTime(now());
            }
        }

        @Nonnull
        public Builder withTime(long time) {
            Check.isTrue(!built);
            state.time = time;
            return this;
        }

        @Nonnull
        public Builder withComment(@Nullable String comment) {
            Check.isTrue(!built);
            state.comment = comment == null ? "" : comment;
            return this;
        }

        @Nonnull
        public HistoryState build() {
            built = true;
            return state;
        }
    }
}
