<?php
/**
* @filename        USPS.php
* @description     This class is for USPS (United States Postal Service)
* @version         1.0
* @package         MSHShippingLibrary.shippingClasses
* @author          Mohammad Sajjad Hossain - info@sajjadhossain.com
*
* @link            http://sajjadhossain.com
* @created on      March 14, 2011
* @Dependencies
* @license
***/

class USPS extends Carrier
{
    private $isDomestic = false;

    public function USPS()
    {
        parent::Carrier();

        $this->setRequestUrl('http://production.shippingapis.com/ShippingAPI.dll');
        $this->setDefaultUserId('600VERTI1538');
        $this->setCacheTableSuffix('usps');
        /*$defaultServices = array('First-Class Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt; Package',
                                'Parcel Post&lt;sup&gt;&amp;reg;&lt;/sup&gt',
                                'Priority Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt;',
                                'Priority Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt; Small Flat Rate Envelope',
                                'Priority Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt; Small Flat Rate Box',
                                'Priority Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt; Large Flat Rate Box',
                                'Priority Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt; Medium Flat Rate Box',
                                'Express Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt;',
                                'Express Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt; Flat Rate Envelope',
                                'Express Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt; International',
                                'First-Class Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt; International Package**',
                                'Priority Mail&lt;sup&gt;&amp;reg;&lt;/sup&gt; International');
        $this->setServices($defaultServices);*/
    }

    public function calculate()
    {
        $this->logMessage('Validating Information for USPS');
        $this->validateServiceSelected();
        $this->validateFromZip();
        $this->validateToZip();
        $this->validateToCountyCode();
        $this->item->validate(array('weight', 'length', 'height', 'width'));

        if($this->isValid())
        {
            if($this->getFromCountryCode() == 'US' && $this->getFromCountryCode() == $this->getToCountryCode())
            {
                $this->isDomestic = true;

                $this->setParam('API', 'RateV4');
            }
            else
            {
                $this->setParam('API', 'IntlRateV2');
            }

            $this->setParam('XML', $this->getXML());
            $this->makeAPICall();
        }

        return $this->response;
    }

    protected function getSearchString()
    {
        if($this->isDomestic)
        {
            $this->searchString = $this->getUserId() . ':'
                                . $this->getFromZip() . ':'
                                . $this->getToZip() . ':'
                                . $this->item->getQuantity() . ':'
                                . $this->item->getWeightInPound() . ':'
                                . $this->item->getLengthInInch() . ':'
                                . $this->item->getWidthInInch() . ':'
                                . $this->item->getHeightInInch() . ':'
                                . $this->getWeightUnit() . ':'
                                . $this->getLengthUnit();
        }
        else
        {
            $this->searchString = $this->getUserId() . ':'
                                . $this->getToCountry() . ':'
                                . $this->item->getQuantity() . ':'
                                . $this->item->getWeightInPound() . ':'
                                . $this->item->getLengthInInch() . ':'
                                . $this->item->getWidthInInch() . ':'
                                . $this->item->getHeightInInch() . ':'
                                . $this->getWeightUnit() . ':'
                                . $this->getLengthUnit();
        }

        return $this->searchString;
    }

    //ORIGINAL IMPLEMENTATION
    /*private function getXML()
    {
        $pounds = $this->item->getWeightInPound();

        $floor = floor($pounds);
        $ounces = 0;

        if($pounds == $floor)
        {
            $pounds = (int) $pounds;
        }
        else
        {
            $ounces = round(($pounds - $floor) * 16, 0);
            $pounds = (int) $floor;
        }

        $girth = (2 * ( $this->item->getLengthInInch() +  $this->item->getWidthInInch()));

        if($this->isDomestic)
        {
            $xml = '<?xml version="1.0"?>
                    <RateV4Request USERID="' . $this->getUserId() . '">';

            $isLarge = $this->item->getLengthInInch() > 12 || $this->item->getWidthInInch() > 12 || $this->item->getHeightInInch() > 12;

            for($i = 1; $i <= $this->item->getQuantity(); $i++)
            {
                $xml .= '<Package ID="' . $i . '">
                            <Service>ALL</Service>
                            <FirstClassMailType>PARCEL</FirstClassMailType>
                            <ZipOrigination>' . $this->getFromZip() . '</ZipOrigination>
                            <ZipDestination>' . $this->getToZip() . '</ZipDestination>
                            <Pounds>' . $pounds . '</Pounds>
                            <Ounces>' . $ounces . '</Ounces>';

                $xml .= $isLarge ? '<Container>RECTANGULAR</Container>' : '<Container />';
                $xml .= '<Size>' . ($isLarge ? 'LARGE' : 'REGULAR') . '</Size>
                            <Width>' . $this->item->getWidthInInch() . '</Width>
                            <Length>' . $this->item->getLengthInInch() . '</Length>
                            <Height>' . $this->item->getHeightInInch() . '</Height>
                            <Girth>' . $girth . '</Girth>
                            <Machinable>true</Machinable>
                        </Package>';
            }

            return $xml . '</RateV4Request>';
        }
        else
        {
            $xml = '<IntlRateV2Request USERID="' . $this->getUserId() . '">';

            $isLarge = $this->item->getLengthInInch() > 12 || $this->item->getWidthInInch() > 12 || $this->item->getHeightInInch() > 12;

            for($i = 1; $i <= $this->item->getQuantity(); $i++)
            {
                $xml .= '<Package ID="' . $i . '">
                            <Pounds>' . $pounds . '</Pounds>
                            <Ounces>' . $ounces . '</Ounces>
                            <Machinable>True</Machinable>
                            <MailType>All</MailType>
                            <ValueOfContents>0.0</ValueOfContents>
                            <Country>' . $this->getToCountry() . '</Country>';
                $xml .= $isLarge ? '<Container>RECTANGULAR</Container>' : '<Container />';
                $xml .= '<Size>' . ($isLarge ? 'LARGE' : 'REGULAR') . '</Size>
                            <Width>' . $this->item->getWidthInInch() . '</Width>
                            <Length>' . $this->item->getLengthInInch() . '</Length>
                            <Height>' . $this->item->getHeightInInch() . '</Height>
                            <Girth>' . $girth . '</Girth>
                         </Package>';
            }

            return $xml . '</IntlRateV2Request>';
        }
    }*/

    //work around
    private function getXML()
    {
        $quantity = $this->item->getQuantity();
        $pounds = $this->item->getWeightInPound();

        $floor = floor($pounds);
        $ounces = 0;

        if($pounds == $floor)
        {
            $pounds = (int) $pounds;
        }
        else
        {
            $ounces = round(($pounds - $floor) * 16, 0);
            $pounds = (int) $floor;
        }

        $totalPounds = $pounds * $quantity;
        $totalOunces = $ounces * $quantity;

        $ounces = $totalOunces % 16;
        $pounds = $totalPounds + (($totalOunces - $ounces)/16);

        $totalLength = $this->item->getLengthInInch();
        $totalWidth = $this->item->getWidthInInch();
        $totalHeight = $this->item->getHeightInInch() * $quantity;

        $girth = (2 * ( $totalLength +  $totalHeight ));

        $isLarge = $totalLength > 12 || $totalWidth > 12 || $totalHeight > 12;

        if($this->isDomestic)
        {
            $xml = '<?xml version="1.0"?>
                    <RateV4Request USERID="' . $this->getUserId() . '">
                        <Package ID="01">
                            <Service>ALL</Service>
                            <FirstClassMailType>PARCEL</FirstClassMailType>
                            <ZipOrigination>' . $this->getFromZip() . '</ZipOrigination>
                            <ZipDestination>' . $this->getToZip() . '</ZipDestination>
                            <Pounds>' . $pounds . '</Pounds>
                            <Ounces>' . $ounces . '</Ounces>';

            $xml .= $isLarge ? '<Container>RECTANGULAR</Container>' : '<Container />';
            return $xml .= '<Size>' . ($isLarge ? 'LARGE' : 'REGULAR') . '</Size>
                            <Width>' . $totalWidth . '</Width>
                            <Length>' . $totalLength . '</Length>
                            <Height>' . $totalHeight . '</Height>
                            <Girth>' . $girth . '</Girth>
                            <Machinable>true</Machinable>
                        </Package>
                    </RateV4Request>';
        }
        else
        {
            $xml = '<IntlRateV2Request USERID="' . $this->getUserId() . '">
                        <Package ID="01">
                            <Pounds>' . $pounds . '</Pounds>
                            <Ounces>' . $ounces . '</Ounces>
                            <Machinable>True</Machinable>
                            <MailType>All</MailType>
                            <ValueOfContents>0.0</ValueOfContents>
                            <Country>' . $this->getToCountry() . '</Country>';
            $xml .= $isLarge ? '<Container>RECTANGULAR</Container>' : '<Container />';

            return $xml .= '<Size>' . ($isLarge ? 'LARGE' : 'REGULAR') . '</Size>
                            <Width>' . $totalWidth . '</Width>
                            <Length>' . $totalLength . '</Length>
                            <Height>' . $totalHeight . '</Height>
                            <Girth>' . $girth . '</Girth>
                         </Package>
                     </IntlRateV2Request>';
        }
    }

    protected function prepareResponse()
    {
        $simpleXML = simplexml_load_string($this->response->getRawResponse());

        if($simpleXML->Number) //general error
        {
            $error = (string) $simpleXML->Description;
            $this->response->setError($error);
        }
        else
        {
            if($this->isDomestic)
            {
                $this->prepareDomesticResponse($simpleXML);
            }
            else
            {
                $this->prepareInternationalResponse($simpleXML);
            }
        }
    }

    /**
    *@desc Prepares response for Domestic rate request
    * @param SimpleXMLElement
    */
    private function prepareDomesticResponse($simpleXML)
    {
        if($simpleXML->Package)
        {
            if(count($simpleXML->Package))
            {
                $allRates = array();

                foreach($simpleXML->Package as $package)
                {
                    if($package->Error)
                    {
                        $error = (string) $package->Error->Description;
                        $this->response->setError($error);
                        return;
                    }

                    $postages = $package->Postage;

                    if(!empty($postages))
                    {
                        $serviceRate = array();

                        foreach($postages as $postage)
                        {
                            $service = (string) $postage->MailService;
                            $rate = (string) $postage->Rate;

                            $serviceRate[$service] = $rate;
                        }

                        $allRates[] = $serviceRate;
                    }
                    else
                    {
                        $this->response->setError('Service not available');
                        return;
                    }
                }
                $common = array();

                foreach($allRates as $rates)
                {
                    if(empty($common))
                    {
                        $common = $rates;
                        continue;
                    }
                    $common = array_intersect_key($rates, $common);
                }
                $common = array_keys($common);

                $shippingRates = array();
                foreach($allRates as $rates)
                {
                    foreach($rates as $key => $value)
                    {               
                        if(in_array($key, $this->getServices()) && in_array($key, $common))
                        {
                            if(empty($shippingRates[$key]))
                            {
                                $shippingRates[$key] = $value;
                            }
                            else
                            {
                                $shippingRates[$key] += $value;
                            }
                        }
                    }
                }

                foreach($shippingRates as $service => $rate)
                {
                    $this->response->setRate(str_replace("#####","&#174;", html_entity_decode(str_replace("&#174;", "#####", $service))), $rate);
                }
                $this->insertResponseToDB();
            }
        }
    }

    /**
    *@desc Prepares response for International rate request
    * @param SimpleXMLElement
    */
    private function prepareInternationalResponse($simpleXML)
    {
        if($simpleXML->Package)
        {
            if(count($simpleXML->Package))
            {
                $allRates = array();

                foreach($simpleXML->Package as $package)
                {
                    if($package->Error)
                    {
                        $error = (string) $package->Error->Description;
                        $this->response->setError($error);
                        return;
                    }

                    $services = $package->Service;

                    if(!empty($services))
                    {
                        $serviceRate = array();

                        foreach($services as $service)
                        {
                            $serviceDesc = (string) $service->SvcDescription;
                            $rate = (string) $service->Postage;

                            $serviceRate[$serviceDesc] = $rate;
                        }

                        $allRates[] = $serviceRate;
                    }
                    else
                    {
                        $this->response->setError('Service not available');
                        return;
                    }
                }

                $common = array();

                foreach($allRates as $rates)
                {
                    if(empty($common))
                    {
                        $common = $rates;
                        continue;
                    }
                    $common = array_intersect_key($rates, $common);
                }

                $common = array_keys($common);

                $shippingRates = array();

                foreach($allRates as $rates)
                {
                    foreach($rates as $key => $value)
                    {
                        if(in_array($key, $this->getServices()) && in_array($key, $common))
                        {
                            if(empty($shippingRates[$key]))
                            {
                                $shippingRates[$key] = $value;
                            }
                            else
                            {
                                $shippingRates[$key] += $value;
                            }
                        }
                    }
                }

                foreach($shippingRates as $service => $rate)
                {
                    $this->response->setRate(str_replace("#####","&#174;", html_entity_decode(str_replace("&#174;", "#####", $service))), number_format($rate, 2));
                }

                $this->insertResponseToDB();
            }
        }
    }
}

//End of file USPS.php