/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.StringTokenizer;

/**
 * Rule manipulates Rule records.
 *
 * @since 1.4
 */
class Rule {

    private List<RuleRec> list;
    private String name;

    /**
     * Constructs a Rule which consists of a Rule record list. The
     * specified name is given to this Rule.
     * @param name the Rule name
     */
    Rule(String name) {
        this.name = name;
        list = new ArrayList<RuleRec>();
    }

    /**
     * Added a RuleRec to the Rule record list.
     */
    void add(RuleRec rec) {
        list.add(rec);
    }

    /**
     * @return the Rule name
     */
    String getName() {
        return name;
    }

    /**
     * Gets all rule records that cover the given year.
     *
     * @param year the year number for which the rule is applicable.
     * @return rules in List that are collated in time. If no rule is found, an empty
     * List is returned.
     */
    List<RuleRec> getRules(int year) {
        List<RuleRec> rules = new ArrayList<RuleRec>(3);
        for (RuleRec rec : list) {
            if (year >= rec.getFromYear() && year <= rec.getToYear()) {
                if ((rec.isOdd() && year % 2 == 0) || (rec.isEven() && year % 2 == 1))
                    continue;
                rules.add(rec);
            }
        }
        int n = rules.size();
        if (n <= 1) {
            return rules;
        }
        if (n == 2) {
            RuleRec rec1 = rules.get(0);
            RuleRec rec2 = rules.get(1);
            if (rec1.getMonthNum() > rec2.getMonthNum()) {
                rules.set(0, rec2);
                rules.set(1, rec1);
            } else if (rec1.getMonthNum() == rec2.getMonthNum()) {
                // TODO: it's not accurate to ignore time types (STD, WALL, UTC)
                long t1 = Time.getLocalTime(year, rec1.getMonth(),
                                            rec1.getDay(), rec1.getTime().getTime());
                long t2 = Time.getLocalTime(year, rec2.getMonth(),
                                            rec2.getDay(), rec2.getTime().getTime());
                if (t1 > t2) {
                    rules.set(0, rec2);
                    rules.set(1, rec1);
                }
            }
            return rules;
        }

        final int y = year;
        RuleRec[] recs = new RuleRec[rules.size()];
        rules.toArray(recs);

        Arrays.sort(recs, new Comparator<RuleRec>() {
                public int compare(RuleRec r1, RuleRec r2) {
                    int n = r1.getMonthNum() - r2.getMonthNum();
                    if (n != 0) {
                        return n;
                    }
                    // TODO: it's not accurate to ignore time types (STD, WALL, UTC)
                    long t1 = Time.getLocalTime(y, r1.getMonth(),
                                                r1.getDay(), r1.getTime().getTime());
                    long t2 = Time.getLocalTime(y, r2.getMonth(),
                                                r2.getDay(), r2.getTime().getTime());
                    return Long.compare(t1, t2);
                }
                public boolean equals(Object o) {
                    return this == o;
                }
            });
        rules.clear();
        for (int i = 0; i < n; i++) {
            if (i != 0 && recs[i -1].getSave() == recs[i].getSave()) {
                // we have two recs back to back with same saving for the same year.
                if (recs[i].isLastRule()) {
                    continue;
                } else if (recs[i - 1].isLastRule()) {
                    rules.remove(rules.size() - 1);
                }
            }
            rules.add(recs[i]);
        }
        return rules;
    }

    /**
     * Gets rule records that have either "max" or cover the endYear
     * value in its DST schedule.
     *
     * @return rules that contain last DST schedule. An empty
     * ArrayList is returned if no last rules are found.
     */
    List<RuleRec> getLastRules() {
        RuleRec start = null;
        RuleRec end = null;

        for (int i = 0; i < list.size(); i++) {
            RuleRec rec = list.get(i);
            if (rec.isLastRule()) {
                if (rec.getSave() > 0) {
                    start = rec;
                } else {
                    end = rec;
                }
            }
        }
        if (start == null || end == null) {
            int endYear = Zoneinfo.getEndYear();
            for (int i  = 0; i < list.size(); i++) {
                RuleRec rec = list.get(i);
                if (endYear >= rec.getFromYear() && endYear <= rec.getToYear()) {
                    if (start == null && rec.getSave() > 0) {
                        start = rec;
                    } else {
                        if (end == null && rec.getSave() == 0) {
                            end = rec;
                        }
                    }
                }
            }
        }

        List<RuleRec> r = new ArrayList<RuleRec>(2);
        if (start == null || end == null) {
            if (start != null || end != null) {
                Main.warning("found last rules for "+name+" inconsistent.");
            }
            return r;
        }

        r.add(start);
        r.add(end);
        return r;
    }
}
