<?php
/*
 * Copyright 2008-2010 GuardTime AS
 *
 * This file is part of the GuardTime PHP SDK.
 *
 * 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
 * distributed 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 the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @package asn1
 * @subpackage tsp
 */

/**
 * TSP Accuracy implementation.
 *
 * <pre>
 * Accuracy ::= SEQUENCE {
 *  seconds        INTEGER              OPTIONAL,
 *  millis     [0] INTEGER  (1..999)    OPTIONAL,
 *  micros     [1] INTEGER  (1..999)    OPTIONAL
 * }
 * </pre>
 *
 * @package asn1
 * @subpackage tsp
 *
 * @link http://tools.ietf.org/html/rfc3161#section-2.4.2 RFC 3161: Time-Stamp Protocol
 */
class TSPAccuracy implements ASN1DEREncodable {

    private $seconds;
    private $millis;
    private $micros;

    /**
     * Constructs a new isntance of TSPAccuracy.
     */
    public function __construct() {
    }

    /**
     * Decodes the given ASN1Sequence as TSPAccuracy.
     *
     * @throws GTException
     * @param  ASN1Sequence $object TSPAccuracy encoded as ASN1Sequence
     * @return void
     */
    public function decode($object) {

        if (!$object instanceof ASN1Sequence) {
            throw new GTException("Expecting an ASN1Sequence");
        }

        if ($object->getObjectCount() < 1 || $object->getObjectCount() > 3) {
            throw new GTException("Invalid number of elements in sequence");
        }

        for ($i = 0; $i < $object->getObjectCount(); $i++) {

            $item = $object->getObjectAt($i);

            if ($item instanceof ASN1Integer) {

                if (!is_null($this->seconds)) {
                    throw new GTException("Unexpected ASN1Integer: {$item->getValue()}");
                }

                if ($i != 0) {
                    throw new GTException("Unexpected ASN1Integer: {$item->getValue()}");
                }

                $this->seconds = $item->getValue();

            } else if ($item instanceof ASN1Tag) {

                switch ($item->getTagValue()) {

                    case 0:

                        // millis

                        if (!is_null($this->millis)) {
                            throw new GTException("Unexpected TAG value: 0");
                        }

                        $millis = $item->getObjectAs(ASN1_TAG_INTEGER);

                        if (!$millis instanceof ASN1Integer) {
                            throw new GTException("Expecting an ASN1Integer");
                        }

                        if ((int) $millis->getValue() < 1) {
                            throw new GTException("Invalid value for millis: {$millis->getValue()}");
                        }

                        if ((int) $millis->getValue() > 999) {
                            throw new GTException("Invalid value for millis: {$millis->getValue()}");
                        }

                        $this->millis = $millis->getValue();

                        break;

                    case 1:

                        // micros

                        if (!is_null($this->micros)) {
                            throw new GTException("Unexpected TAG value: 1");
                        }

                        $micros = $item->getObjectAs(ASN1_TAG_INTEGER);

                        if (!$micros instanceof ASN1Integer) {
                            throw new GTException("Expecting an ASN1Integer");
                        }

                        if ((int) $micros->getValue() < 1) {
                            throw new GTException("Invalid value for micros: {$micros->getValue()}");
                        }

                        if ((int) $micros->getValue() > 999) {
                            throw new GTException("Invalid value for micros: {$micros->getValue()}");
                        }

                        $this->micros = $micros->getValue();

                    default:
                        throw new GTException("Unexpected TAG value: {$item->getTagValue()}");

                }

            } else {
                throw new GTException("Unexpected ASN1 type: " . get_class($item));

            }

        }

    }

    /**
     * Encodes this TSPAccuracy using DER.
     *
     * @return array byte array that contains the DER encoding of this TSPAccuracy
     */
    public function encodeDER() {

        $sequence = new ASN1Sequence();

        if ($this->seconds != null) {
            $sequence->add(new ASN1Integer($this->seconds));
        }

        if ($this->millis != null) {
            $tag = new ASN1Tag();
            $tag->setExplicit(true);
            $tag->setTagClass(ASN1_TAG_CONTEXT);
            $tag->setTagType(ASN1_TAG_CONSTRUCTED);
            $tag->setTagValue(0);
            $tag->setObject(new ASN1Integer($this->millis));

            $sequence->add($tag);
        }

        if ($this->micros != null) {
            $tag = new ASN1Tag();
            $tag->setExplicit(true);
            $tag->setTagClass(ASN1_TAG_CONTEXT);
            $tag->setTagType(ASN1_TAG_CONSTRUCTED);
            $tag->setTagValue(1);
            $tag->setObject(new ASN1Integer($this->micros));

            $sequence->add($tag);
        }

        return $sequence->encodeDER();

    }

    /**
     * Gets this TSPAccuracy formatted as string.
     *
     * @return string formatted TSPAccuracy
     */
    public function getFormatted() {

        $accuracy = new GTBigInteger(0);

        if (!is_null($this->seconds)) {
            $accuracy = $accuracy->add(new GTBigInteger($this->seconds));
        }

        $accuracy = $accuracy->mul(new GTBigInteger(1000));

        if (!is_null($this->millis)) {
            $accuracy = $accuracy->add(new GTBigInteger($this->millis));
        }

        $accuracy = $accuracy->mul(new GTBigInteger(1000));

        if (!is_null($this->micros)) {
            $accuracy = $accuracy->add(new GTBigInteger($this->micros));
        }

        $zero = new GTBigInteger(0);

        if ($accuracy->mod(1000000)->comp($zero) == 0) {
            return "{$accuracy->div(new GTBigInteger(1000000))->getValue()}s";

        } else if ($accuracy->mod(1000)->comp($zero) == 0) {
            return "{$accuracy->div(new GTBigInteger(1000))->getValue()}ms";

        } else {
            return "{$accuracy->getValue()}us";

        }
    }

}

?>
