// Copyright 2015 The Project Buendia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distrib-
// uted under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied.  See the License for
// specific language governing permissions and limitations under the License.

package org.projectbuendia.client.models;

import android.content.ContentValues;
import android.database.Cursor;
import android.support.annotation.NonNull;

import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.json.JSONException;
import org.json.JSONObject;
import org.projectbuendia.client.json.JsonOrder;
import org.projectbuendia.client.providers.Contracts;
import org.projectbuendia.client.utils.Utils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

/** An order in the app model. */
@Immutable
public final class Order extends Base<String> implements Comparable<Order> {
    public static final char NON_BREAKING_SPACE = '\u00a0';
    public final
    @Nullable String uuid;
    public final String patientUuid;
    public final String instructions;
    public final DateTime start;
    public final
    @Nullable DateTime stop;

    public static Order fromJson(JsonOrder order) {
        return new Order(order.uuid, order.patient_uuid, order.instructions,
            order.start_millis, order.stop_millis);
    }

    // TODO/robustness: Store medication, dosage, and frequency as separate fields instead of
    // mashing them into one free-text instructions field.  This will also enable
    // internationalization.

    // [Any amount of non-whitespace][space][any text][space][more than one digit]x[space][any text]
    // OR [Any amount of non-whitespace][an optional space][any text]
    // Note that the second branch will match everything - even the empty string - and shoehorn
    // the order instructions into the medication and dosage fields.
    public static final Pattern INSTRUCTIONS_PATTERN = Pattern.compile(
        "([^ ]*) (.*) ([0-9]+)x .*|([^ ]*) ?(.*)");

    public static String getInstructions(String medication, String dosage, String frequency) {
        medication = Utils.valueOrDefault(medication, "");
        dosage = Utils.valueOrDefault(dosage, "");
        frequency = Utils.valueOrDefault(frequency, "");
        if (!frequency.isEmpty()) {
            frequency += "x daily";
        }
        return (medication.replace(' ', NON_BREAKING_SPACE) + " " + dosage + " " + frequency)
                .trim();
    }

    public static String getMedication(String instructions) {
        Matcher matcher = INSTRUCTIONS_PATTERN.matcher(Utils.valueOrDefault(instructions, ""));
        if (matcher.matches()) {
            // These groups are the "([^ ]*)" in each branch of the regex above.
            String group = Utils.valueOrDefault(matcher.group(1), matcher.group(4));
            return group.replace(NON_BREAKING_SPACE, ' ');
        }
        return null;
    }

    public static String getDosage(String instructions) {
        Matcher matcher = INSTRUCTIONS_PATTERN.matcher(Utils.valueOrDefault(instructions, ""));
        if (matcher.matches()) {
            // These groups are the `(.*)` in each branch of the regex.
            return Utils.valueOrDefault(matcher.group(2), matcher.group(5));
        }
        return null;
    }

    public static String getFrequency(String instructions) {
        Matcher matcher = INSTRUCTIONS_PATTERN.matcher(Utils.valueOrDefault(instructions, ""));
        if (matcher.matches()) {
            return matcher.group(3);
        }
        return null;
    }

    public Order(@Nullable String uuid, String patientUuid,
                 String instructions, DateTime start, @Nullable DateTime stop) {
        this.uuid = uuid;
        this.patientUuid = patientUuid;
        this.instructions = instructions;
        this.start = start;
        this.stop = stop;
    }

    public Order(@Nullable String uuid, String patientUuid,
                 String instructions, Long startMillis, @Nullable Long stopMillis) {
        this.uuid = uuid;
        this.patientUuid = patientUuid;
        this.instructions = instructions;
        this.start = new DateTime(startMillis);
        this.stop = stopMillis == null ? null : new DateTime(stopMillis);
    }

    public String getMedication() {
        return getMedication(instructions);
    }

    public String getDosage() {
        return getDosage(instructions);
    }

    public String getFrequency() {
        return getFrequency(instructions);
    }

    public Interval getInterval() {
        return Utils.toInterval(start, stop);
    }

    @Override public int compareTo(@NonNull Order other) {
        int result = start.compareTo(other.start);
        result = result != 0 ? result : instructions.compareTo(other.instructions);
        return result;
    }

    public JSONObject toJson() throws JSONException {
        JSONObject json = new JSONObject();
        json.put("patient_uuid", patientUuid);
        json.put("instructions", instructions);
        json.put("start_millis", start.getMillis());
        json.put("stop_millis", stop == null ? null : stop.getMillis());
        return json;
    }

    public ContentValues toContentValues() {
        ContentValues cv = new ContentValues();
        cv.put(Contracts.Orders.UUID, uuid);
        cv.put(Contracts.Orders.PATIENT_UUID, patientUuid);
        cv.put(Contracts.Orders.INSTRUCTIONS, instructions);
        cv.put(Contracts.Orders.START_MILLIS, start.getMillis());
        cv.put(Contracts.Orders.STOP_MILLIS, stop == null ? null : stop.getMillis());
        return cv;
    }

    /** An {@link CursorLoader} that reads a Cursor and creates an {@link Order}. */
    @Immutable
    public static class Loader implements CursorLoader<Order> {
        @Override public Order fromCursor(Cursor cursor) {
            return new Order(
                cursor.getString(cursor.getColumnIndex(Contracts.Orders.UUID)),
                cursor.getString(cursor.getColumnIndex(Contracts.Orders.PATIENT_UUID)),
                cursor.getString(cursor.getColumnIndex(Contracts.Orders.INSTRUCTIONS)),
                cursor.getLong(cursor.getColumnIndex(Contracts.Orders.START_MILLIS)),
                cursor.getLong(cursor.getColumnIndex(Contracts.Orders.STOP_MILLIS))
            );
        }
    }
}

@@ -5453,32 +5453,118 @@
 t.getMillis());%0A
+        // Use %60JSONObject.NULL%60 instead of %60null%60 so that the value is actually set.%0A
         json.put
@@ -5586,36 +5586,47 @@
  stop == null ? 
-null
+JSONObject.NULL
  : stop.getMilli

Fix bug where Order end time wasn't saving.