/*
    Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
    и других спецификаций для функционирования компактных приложений на языке
    Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

    Copyright © 2016–2017, 2019–2022 Малик Разработчик

    Это свободная программа: вы можете перераспространять ее и/или изменять
    ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она будет полезной,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <https://www.gnu.org/licenses/>.
*/

package java.util;

public class Timer extends Object
{
    private TimerThread thread;

    public Timer() {
        TimerThread thread;
        this.thread = thread = new TimerThread();
        ((Thread) thread).start();
    }

    public void cancel() {
        TimerThread thread;
        if((thread = this.thread) != null)
        {
            this.thread = null;
            thread.terminate();
        }
    }

    public void schedule(TimerTask task, long delay) {
        long firstExecutionTime;
        TimerThread thread;
        if(delay < 0L || (firstExecutionTime = System.currentTimeMillis() + delay) < 0L)
        {
            throw new IllegalArgumentException("Timer.schedule: аргумент delay имеет недопустимое значение.");
        }
        if((thread = this.thread) == null)
        {
            throw new IllegalStateException("Timer.schedule: планирование новых задач было отменено.");
        }
        if(task != null) thread.schedule(task, firstExecutionTime);
    }

    public void schedule(TimerTask task, long delay, long period) {
        long firstExecutionTime;
        TimerThread thread;
        if(delay < 0L || (firstExecutionTime = System.currentTimeMillis() + delay) < 0L)
        {
            throw new IllegalArgumentException("Timer.schedule: аргумент delay имеет недопустимое значение.");
        }
        if((thread = this.thread) == null)
        {
            throw new IllegalStateException("Timer.schedule: планирование новых задач было отменено.");
        }
        if(task != null) thread.schedule(task, firstExecutionTime, period, false);
    }

    public void schedule(TimerTask task, Date time) {
        long firstExecutionTime;
        TimerThread thread;
        if((firstExecutionTime = time == null ? System.currentTimeMillis() : time.getTime()) < 0L)
        {
            throw new IllegalArgumentException("Timer.schedule: аргумент time имеет недопустимое значение.");
        }
        if((thread = this.thread) == null)
        {
            throw new IllegalStateException("Timer.schedule: планирование новых задач было отменено.");
        }
        if(task != null) thread.schedule(task, firstExecutionTime);
    }

    public void schedule(TimerTask task, Date firstTime, long period) {
        long firstExecutionTime;
        TimerThread thread;
        if((firstExecutionTime = firstTime == null ? System.currentTimeMillis() : firstTime.getTime()) < 0L)
        {
            throw new IllegalArgumentException("Timer.schedule: аргумент firstTime имеет недопустимое значение.");
        }
        if((thread = this.thread) == null)
        {
            throw new IllegalStateException("Timer.schedule: планирование новых задач было отменено.");
        }
        if(task != null) thread.schedule(task, firstExecutionTime, period, false);
    }

    public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
        long firstExecutionTime;
        TimerThread thread;
        if(delay < 0L || (firstExecutionTime = System.currentTimeMillis() + delay) < 0L)
        {
            throw new IllegalArgumentException("Timer.scheduleAtFixedRate: аргумент delay имеет недопустимое значение.");
        }
        if((thread = this.thread) == null)
        {
            throw new IllegalStateException("Timer.scheduleAtFixedRate: планирование новых задач было отменено.");
        }
        if(task != null) thread.schedule(task, firstExecutionTime, period, true);
    }

    public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
        long firstExecutionTime;
        TimerThread thread;
        if((firstExecutionTime = firstTime == null ? System.currentTimeMillis() : firstTime.getTime()) < 0L)
        {
            throw new IllegalArgumentException("Timer.scheduleAtFixedRate: аргумент firstTime имеет недопустимое значение.");
        }
        if((thread = this.thread) == null)
        {
            throw new IllegalStateException("Timer.scheduleAtFixedRate: планирование новых задач было отменено.");
        }
        if(task != null) thread.schedule(task, firstExecutionTime, period, true);
    }
}

final class TimerThread extends Thread
{
    private static final int STATE_MASK = 0x03;
    private static final int STATE_FREE = 0x00;
    private static final int STATE_SCHEDULED = 0x01;
    private static final int STATE_EXECUTING = 0x02;
    private static final int STATE_CANCELLED = 0x03;
    private static final int FLAG_PERIODIC = 0x04;
    private static final int FLAG_FIXED_RATE = 0x08;

    private static final Object MONITOR;

    static {
        MONITOR = new Object();
    }

    private boolean terminated;
    private int length;
    private TimerTask[] queue;
    private final Object monitor;

    public TimerThread() {
        super("Поток событий тимера");
        this.queue = new TimerTask[8];
        this.monitor = new Object();
    }

    public void run() {
        for(Object monitor = this.monitor; ; )
        {
            boolean fixedRate;
            boolean mustContinue;
            TimerTask task;
            waitTask();
            if(terminated) break;
            if((task = pop()) == null) continue;
            mustContinue = false;
            synchronized(monitor)
            {
                label0:
                {
                    int flags;
                    fixedRate = ((flags = task.flags) & FLAG_FIXED_RATE) != 0;
                    if((flags & STATE_MASK) != STATE_SCHEDULED)
                    {
                        task.flags = STATE_CANCELLED;
                        task.period = 0L;
                        task.nextExecutionTime = 0L;
                        task.shedExecutionTime = 0L;
                        task.owner = null;
                        mustContinue = true;
                        break label0;
                    }
                    task.flags = flags & ~STATE_MASK | STATE_EXECUTING;
                    task.shedExecutionTime = task.nextExecutionTime;
                    if(fixedRate) task.nextExecutionTime += task.period;
                }
            }
            if(mustContinue) continue;
            try
            {
                task.run();
            }
            catch(RuntimeException e)
            {
                e.printRealStackTrace();
            }
            synchronized(monitor)
            {
                label0:
                {
                    int flags;
                    if(((flags = task.flags) & STATE_MASK) != STATE_EXECUTING || (flags & FLAG_PERIODIC) == 0)
                    {
                        task.flags = STATE_CANCELLED;
                        task.period = 0L;
                        task.nextExecutionTime = 0L;
                        task.shedExecutionTime = 0L;
                        task.owner = null;
                        break label0;
                    }
                    if(!fixedRate) task.nextExecutionTime = System.currentTimeMillis() + task.period;
                    task.flags = flags & ~STATE_MASK | STATE_SCHEDULED;
                    push(task);
                }
            }
        }
        clear();
    }

    public void terminate() {
        Object monitor;
        synchronized(monitor = this.monitor)
        {
            terminated = true;
            monitor.notify();
        }
    }

    public void schedule(TimerTask task, long time) {
        int error = 0;
        Object monitor;
        synchronized(MONITOR)
        {
            label0:
            {
                if((task.flags & STATE_MASK) != STATE_FREE)
                {
                    error = 1;
                    break label0;
                }
                task.flags = STATE_SCHEDULED;
                task.period = 0L;
            }
        }
        if(error == 1)
        {
            throw new IllegalStateException("Timer.schedule: задача уже планировалась раннее.");
        }
        synchronized(monitor = this.monitor)
        {
            task.nextExecutionTime = time;
            task.owner = this;
            push(task);
            monitor.notify();
        }
    }

    public void schedule(TimerTask task, long time, long period, boolean fixedRate) {
        int error = 0;
        Object monitor;
        synchronized(MONITOR)
        {
            label0:
            {
                if((task.flags & STATE_MASK) != STATE_FREE)
                {
                    error = 1;
                    break label0;
                }
                task.flags = fixedRate ? FLAG_FIXED_RATE | FLAG_PERIODIC | STATE_SCHEDULED : FLAG_PERIODIC | STATE_SCHEDULED;
                task.period = period >= 0L ? period : 0L;
            }
        }
        if(error == 1)
        {
            throw new IllegalStateException(fixedRate ? "Timer.scheduleAtFixedRate: задача уже планировалась раннее." : "Timer.schedule: задача уже планировалась раннее.");
        }
        synchronized(monitor = this.monitor)
        {
            task.nextExecutionTime = time;
            task.owner = this;
            push(task);
            monitor.notify();
        }
    }

    public boolean cancel(TimerTask task) {
        boolean result;
        Object monitor;
        synchronized(monitor = this.monitor)
        {
            label0:
            {
                int flags;
                int index;
                int length;
                TimerTask[] queue;
                if(task.owner != this)
                {
                    result = false;
                    break label0;
                }
                result = ((flags = task.flags) & FLAG_PERIODIC) != 0 || (flags & STATE_MASK) == STATE_SCHEDULED;
                if((index = Array.findb(queue = this.queue, length = this.length - 1, task)) < 0)
                {
                    task.flags = STATE_CANCELLED;
                    task.period = 0L;
                    task.nextExecutionTime = 0L;
                    task.shedExecutionTime = 0L;
                    task.owner = null;
                    break label0;
                }
                if(index < length) Array.copy(queue, index + 1, queue, index, length - index);
                queue[this.length = length] = null;
                task.flags = STATE_CANCELLED;
                task.period = 0L;
                task.nextExecutionTime = 0L;
                task.shedExecutionTime = 0L;
                task.owner = null;
                monitor.notify();
            }
        }
        return result;
    }

    private void clear() {
        synchronized(monitor)
        {
            TimerTask[] queue = this.queue;
            for(int i = length; i-- > 0; )
            {
                TimerTask task;
                (task = queue[i]).flags = STATE_CANCELLED;
                task.period = 0L;
                task.nextExecutionTime = 0L;
                task.shedExecutionTime = 0L;
                task.owner = null;
                queue[i] = null;
            }
            length = 0;
        }
    }

    private void waitTask() {
        Object monitor;
        synchronized(monitor = this.monitor)
        {
            for(; ; )
            {
                int length = this.length;
                TimerTask[] queue = this.queue;
                try
                {
                    long remainingTime;
                    if(length <= 0)
                    {
                        monitor.wait();
                    }
                    else if((remainingTime = queue[length - 1].nextExecutionTime - System.currentTimeMillis()) > 0L)
                    {
                        monitor.wait(remainingTime);
                    }
                    break;
                }
                catch(InterruptedException e)
                {
                    e.printRealStackTrace();
                }
            }
        }
    }

    private void push(TimerTask task) {
        int length;
        int index;
        long time;
        TimerTask[] queue;
        if((length = this.length) == (queue = this.queue).length) Array.copy(queue, 0, queue = this.queue = new TimerTask[length << 1], 0, length);
        index = length;
        time = task.nextExecutionTime;
        for(int i = 0; i < length; i++) if(queue[i].nextExecutionTime <= time)
        {
            Array.copy(queue, index = i, queue, index + 1, length - index);
            break;
        }
        queue[index] = task;
        this.length = length + 1;
    }

    private TimerTask pop() {
        TimerTask result;
        synchronized(monitor)
        {
            int length;
            TimerTask[] queue;
            if((length = this.length - 1) >= 0 && (result = (queue = this.queue)[length]).nextExecutionTime <= System.currentTimeMillis())
            {
                queue[this.length = length] = null;
            } else
            {
                result = null;
            }
        }
        return result;
    }
}
