<?php
namespace Avalara;
use GuzzleHttp\Client;

/*****************************************************************************
 *                  Transaction Builder Convenience Object                   *
 *                                                                           *
 *    This file is not automatically generated by the AvaTax SDK process.    *
 *                        You may edit this file.                            *
 *                                                                           *
 *****************************************************************************/
/**
 * TransactionBuilder helps you construct a new transaction using a literate interface
 */
class TransactionBuilder
{
    /**
     * The in-progress model
     */
    private $_model;

    /**
     * Keeps track of the line number when adding multiple lines
     */
    private $_line_number;

    /**
     * The client that will be used to create the transaction
     */
    private $_client;

    /**
     * TransactionBuilder helps you construct a new transaction using a literate interface
     *
     * @param AvaTaxClient  $client        The AvaTaxClient object to use to create this transaction
     * @param string        $companyCode   The code of the company for this transaction
     * @param string        $type          The type of transaction to create (See DocumentType::* for a list of allowable values)
     * @param string        $customerCode  The customer code for this transaction
     * @param string|null   $dateTime      The datetime of the transaction, defaults to current time when null (Format: Y-m-d)
     */
    public function __construct($client, $companyCode, $type, $customerCode, $dateTime = null)
    {
        $this->_client = $client;
        $this->_line_number = 1;
        $this->_model = [
            'companyCode' => $companyCode,
            'customerCode' => $customerCode,
            'date' => $dateTime !== null ? $dateTime : date('Y-m-d'),
            'type' => $type,
            'lines' => [],
        ];
    }

    /**
     * Set the commit flag of the transaction.
     *
     * @return
     */
    public function withCommit()
    {
        $this->_model['commit'] = true;
        return $this;
    }

    /**
     * Enable diagnostic information
     *
     * @return  TransactionBuilder
     */
    public function withDiagnostics()
    {
        $this->_model['debugLevel'] = TaxDebugLevel::C_DIAGNOSTIC;
        return $this;
    }

    /**
     * Set a specific discount amount
     *
     * @param   float               $discount
     * @return  TransactionBuilder
     */
    public function withDiscountAmount($discount)
    {
        $this->_model['discount'] = $discount;
        return $this;
    }

    /**
     * Set if discount is applicable for the current line
     *
     * @param   boolean             discounted
     * @return  TransactionBuilder
     */
    public function withItemDiscount($discounted)
    {
        $li = $this->getMostRecentLineIndex();
        $this->_model['lines'][$li]['discounted'] = $discounted;
        return $this;
    }

    /**
     * Set a specific transaction code
     *
     * @param   string              code
     * @return  TransactionBuilder
     */
    public function withTransactionCode($code)
    {
        $this->_model['code'] = $code;
        return $this;
    }

    /**
     * Set the document type
     *
     * @param   string              type    (See DocumentType::* for a list of allowable values)
     * @return  TransactionBuilder
     */
    public function withType($type)
    {
        $this->_model['type'] = $type;
        return $this;
    }

    /**
     * Set VAT business identification number for customer
     *
     * @param   string              no
     * @return TransactionBuilder
     */
    public function withBusinessIdentificationNo($no)
    {
        $this->_model['businessIdentificationNo'] = $no;
        return $this;
    }

    /**
     * Set a customer email
     *
     * @param   string              email
     * @return  TransactionBuilder
     */
    public function withEmail($email)
    {
        $this->_model['email'] = $email;
        return $this;
    }

    /**
     * Set client application customer or usage type
     *
     * @param   string              code    (See API endpoint `/api/v2/definitions/entityusecodes` for a list of allowable values)
     * @return TransactionBuilder
     */
    public function withEntityUseCode($code)
    {
        $this->_model['entityUseCode'] = $code;
        return $this;
    }

    /**
     * Set purchase order number
     *
     * @param   string              no
     * @return TransactionBuilder
     */
    public function withPurchaseOrderNo($no)
    {
        $this->_model['purchaseOrderNo'] = $no;
        return $this;
    }

    /**
     * Set customer-provided reference code
     *
     * @param   string              code
     * @return TransactionBuilder
     */
    public function withReferenceCode($code)
    {
        $this->_model['referenceCode'] = $code;
        return $this;
    }

    /**
     * Set the sale location code for reporting this document to the tax authority
     *
     * @param   string              code
     * @return TransactionBuilder
     */
    public function withReportingLocationCode($code)
    {
        $this->_model['reportingLocationCode'] = $code;
        return $this;
    }

    /**
     * Set flag for seller as importer of record
     *
     * @param bool $isSeller
     *
     * @return TransactionBuilder
     */
    public function withSellerIsImporterOfRecord($isSeller = true)
    {
        $this->_model['isSellerImporterOfRecord'] = $isSeller;
        return $this;
    }

    /**
     * Set exchange rate information
     *
     * @param   float               rate
     * @param   date                effectiveDate
     * @return TransactionBuilder
     */
    public function withExchangeRate($rate, $effectiveDate = null)
    {
        $this->_model['exchangeRate'] = $rate;
        if ($effectiveDate) {
            $this->_model['exchangeRateEffectiveDate'] = $effectiveDate;
        }
        return $this;
    }

     /**
     * Specific a exchange rate currency code for this transaction
     *
     * @param   string              $exchangeRateCurrencyCode Specific the three-character ISO 4217 code that is being used for exchange rate for this transaction (optional)
     * @return  TransactionBuilder
     */
    public function withExchangeRateCurrencyCode($exchangeRateCurrencyCode)
    {
        $this->_model['exchangeRateCurrencyCode'] = $exchangeRateCurrencyCode;

        return $this;
    }
       
    /**
     * Add a parameter at the document level
     *
     * @param   string              name
     * @param   string              value
     * @return  TransactionBuilder
     */
    public function withParameter($name, $value)
    {
        if (empty($this->_model['parameters'])) $this->_model['parameters'] = [];
        $this->_model['parameters'][$name] = $value;
        return $this;
    }

    /**
     * Add a parameter to the current line
     *
     * @param   string              name
     * @param   string              value
     * @return  TransactionBuilder
     */
    public function withLineParameter($name, $value)
    {
        $li = $this->getMostRecentLineIndex();
        if (empty($this->_model['lines'][$li]['parameters'])) {
            $this->_model['lines'][$li]['parameters'] = [];
        }
        $this->_model['lines'][$li]['parameters'][$name] = $value;
        return $this;
    }

        /**
     * Add a parameter to the current line when unit is specified
     *
     * @param   string              name
     * @param   string              value
     * @param   string              unit
     * @return  TransactionBuilder
     */
    public function withLineParameterAndUnit($name, $value, $unit)
    {
        $li = $this->getMostRecentLineIndex();
        if (empty($this->_model['lines'][$li]['parameters'])) {
            $this->_model['lines'][$li]['parameters'] = [];
        }
        $l = [
            'name' => $name,
            'value' => $value,
            'unit' => $unit
        ];
        $this->_model['lines'][$li]['parameters'][] = $l;
        return $this;
    }

    /**
     * Add a user defined field to the current line.
     *
     * @param string $name  The name of the user defined field.
     * @param string $value The value for the user defined field.
     * @return TransactionBuilder
     * @throws \Exception if no line exists.
     */
    public function withLineUserDefinedField($name, $value)
    {
        $li = $this->getMostRecentLineIndex();
        if (empty($this->_model['lines'][$li]['userDefinedFields'])) {
            $this->_model['lines'][$li]['userDefinedFields'] = [];
        }
        $this->_model['lines'][$li]['userDefinedFields'][] = [
            'name'  => $name,
            'value' => $value
        ];
        return $this;
    }

    /**
     * Add an address to this transaction
     *
     * @param   string              type          Address Type (See AddressType::* for a list of allowable values)
     * @param   string              line1         The street address, attention line, or business name of the location.
     * @param   string              line2         The street address, business name, or apartment/unit number of the location.
     * @param   string              line3         The street address or apartment/unit number of the location.
     * @param   string              city          City of the location.
     * @param   string              region        State or Region of the location.
     * @param   string              postalCode    Postal/zip code of the location.
     * @param   string              country       The two-letter country code of the location.
     * @return  TransactionBuilder
     */
    public function withAddress($type, $line1, $line2, $line3, $city, $region, $postalCode, $country)
    {
        if (empty($this->_model['addresses'])) $this->_model['addresses'] = [];
        $ai = [
            'line1' => $line1,
            'line2' => $line2,
            'line3' => $line3,
            'city' => $city,
            'region' => $region,
            'postalCode' => $postalCode,
            'country' => $country
        ];
        $this->_model['addresses'][$type] = $ai;
        return $this;
    }
    
    /**
     * Add an address to this transaction using an existing company location code.
     * 
     * AvaTax will search for a company location whose code matches the `locationCode` parameter and use the address
     * of that location.
     *
     * @param   string              type          Address Type (See AddressType::* for a list of allowable values)
     * @param   string              locationCode  The location code of the address
     * @return  TransactionBuilder
     */
    public function WithAddressLocationCode($type, $locationCode)
    {
        if (empty($this->_model['addresses'])) $this->_model['addresses'] = [];
        $ai = [
            'locationCode' => $locationCode
        ];
        $this->_model['addresses'][$type] = $ai;
        return $this;
    }

    /**
     * Add a lat/long coordinate to this transaction
     *
     * @param   string              $type       Address Type (See AddressType::* for a list of allowable values)
     * @param   float               $latitude   The latitude of the geolocation for this transaction
     * @param   float               $longitude  The longitude of the geolocation for this transaction
     * @return  TransactionBuilder
     */
    public function withLatLong($type, $latitude, $longitude)
    {
        $this->_model['addresses'][$type] = [
            'latitude' => $latitude,
            'longitude' => $longitude
        ];
        return $this;
    }

    /**
     * Add an address to this line
     *
     * @param   string              type        Address Type (See AddressType::* for a list of allowable values)
     * @param   string              line1       The street address, attention line, or business name of the location.
     * @param   string              line2       The street address, business name, or apartment/unit number of the location.
     * @param   string              line3       The street address or apartment/unit number of the location.
     * @param   string              city        City of the location.
     * @param   string              region      State or Region of the location.
     * @param   string              postalCode  Postal/zip code of the location.
     * @param   string              country     The two-letter country code of the location.
     * @return  TransactionBuilder
     */
    public function withLineAddress($type, $line1, $line2, $line3, $city, $region, $postalCode, $country)
    {
        $li = $this->getMostRecentLineIndex();
        $this->_model['lines'][$li]['addresses'][$type] = [
            'line1' => $line1,
            'line2' => $line2,
            'line3' => $line3,
            'city' => $city,
            'region' => $region,
            'postalCode' => $postalCode,
            'country' => $country
        ];
        return $this;
    }

    /**
     * Add a merchant seller ID to this line
     *
     * @param   string              merchantSellerIdentifier        ID of merchant seller
     * @return  TransactionBuilder
     */
    public function withLineMerchantSeller($merchantSellerIdentifier)
    {
        $li = $this->getMostRecentLineIndex();
        $this->_model['lines'][$li]['merchantSellerIdentifier'] = $merchantSellerIdentifier;
        return $this;
    }

    /**
     * Add a document-level Tax Override to the transaction.
     *  - A TaxDate override requires a valid DateTime object to be passed.
     * TODO: Verify Tax Override constraints and add exceptions.
     *
     * @param   string              $type       Type of the Tax Override (See TaxOverrideType::* for a list of allowable values)
     * @param   string              $reason     Reason of the Tax Override.
     * @param   float               $taxAmount  Amount of tax to apply. Required for a TaxAmount Override.
     * @param   date                $taxDate    Date of a Tax Override. Required for a TaxDate Override.
     * @return  TransactionBuilder
     */
    public function withTaxOverride($type, $reason, $taxAmount, $taxDate)
    {
        $this->_model['taxOverride'] = [
            'type' => $type,
            'reason' => $reason,
            'taxAmount' => $taxAmount,
            'taxDate' => $taxDate
        ];

        // Continue building
        return $this;
    }

    /**
     * Add a line-level Tax Override to the current line.
     *  - A TaxDate override requires a valid DateTime object to be passed.
     * TODO: Verify Tax Override constraints and add exceptions.
     *
     * @param   string              $type        Type of the Tax Override (See TaxOverrideType::* for a list of allowable values)
     * @param   string              $reason      Reason of the Tax Override.
     * @param   float               $taxAmount   Amount of tax to apply. Required for a TaxAmount Override.
     * @param   date                $taxDate     Date of a Tax Override. Required for a TaxDate Override.
     * @return  TransactionBuilder
     */
    public function withLineTaxOverride($type, $reason, $taxAmount, $taxDate)
    {
        // Address the DateOverride constraint.
        if (($type == TaxOverrideType::C_TAXDATE) && (empty($taxDate))) {
            throw new \Exception("A valid date is required for a Tax Date Tax Override.");
        }

        $li = $this->getMostRecentLineIndex();
        $this->_model['lines'][$li]['taxOverride'] = [
            'type' => $type,
            'reason' => $reason,
            'taxAmount' => $taxAmount,
            'taxDate' => $taxDate
        ];

        // Continue building
        return $this;
    }

    /**
     * Set description for current line
     *
     * @param   string              description
     * @return TransactionBuilder
     * @throws \Exception
     */
    public function withLineDescription($description)
    {
        $li = $this->getMostRecentLineIndex();
        $this->_model['lines'][$li]['description'] = $description;

        return $this;
    }

    /**
     * Set flag on current line to indicate tax is included
     *
     * @return TransactionBuilder
     * @throws \Exception
     */
    public function withLineTaxIncluded()
    {
        $li = $this->getMostRecentLineIndex();
        $this->_model['lines'][$li]['taxIncluded'] = true;

        return $this;
    }

    /**
     * Set customer defined fields for current line
     *
     * @param   string              ref1
     * @param   string              ref2
     * @return TransactionBuilder
     * @throws \Exception
     */
    public function withLineCustomFields($ref1, $ref2 = null)
    {
        $li = $this->getMostRecentLineIndex();
        $this->_model['lines'][$li]['ref1'] = $ref1;
        if ($ref2) {
            $this->_model['lines'][$li]['ref2'] = $ref2;
        }

        return $this;
    }

    /**
     * Set HS code for current line
     *
     * @param   string              hsCode
     * @return  TransactionBuilder
     * @throws  \Exception
     */
    public function withLineHsCode($hsCode)
    {
        $li = $this->getMostRecentLineIndex();
        $this->_model['lines'][$li]['hsCode'] = $hsCode;

        return $this;
    }

    /**
     * Add a line to this transaction
     *
     * @param   float  $amount      Value of the item.
     * @param   float  $quantity    Quantity of the item.
     * @param   string $itemCode
     * @param   string $taxCode     Tax Code of the item.
        - If itemCode is blank and taxCode is left blank, the default item (P0000000) is assumed.
        - If itemCode is mapped to a taxCode in AvaTax Management System then the mapped taxCode
          takes precedence, even if left blank or provided with any other value.
     * @param   string $lineNumber  Custom Line number, defaults to auto-incremented number if null
     * @return  TransactionBuilder
     */
    public function withLine($amount, $quantity, $itemCode, $taxCode, $lineNumber = null)
    {
        if($lineNumber === null) {
            $lineNumber = $this->_line_number;
        }
        $l = [
            'number' => $lineNumber,
            'quantity' => $quantity,
            'amount' => $amount,
            'taxCode' => $taxCode,
            'itemCode' => $itemCode
        ];
        array_push($this->_model['lines'], $l);
        $this->_line_number++;

        // Continue building
        return $this;
    }

    /**
     * Add a line to this transaction
     *
     * @param   float               $amount      Value of the line
     * @param   string              $type        Address Type  (See AddressType::* for a list of allowable values)
     * @param   string              $line1       The street address, attention line, or business name of the location.
     * @param   string              $line2       The street address, business name, or apartment/unit number of the location.
     * @param   string              $line3       The street address or apartment/unit number of the location.
     * @param   string              $city        City of the location.
     * @param   string              $region      State or Region of the location.
     * @param   string              $postalCode  Postal/zip code of the location.
     * @param   string              $country     The two-letter country code of the location.
     * @param   string              $lineNumber  Custom Line number, defaults to auto-incremented number if null
     * @return  TransactionBuilder
     */
    public function withSeparateAddressLine($amount, $type, $line1, $line2, $line3, $city, $region, $postalCode,
        $country, $lineNumber = null)
    {
        if($lineNumber === null) {
            $lineNumber = $this->_line_number;
        }
        $l = [
            'number' => $lineNumber,
            'quantity' => 1,
            'amount' => $amount,
            'addresses' => [
                $type => [
                    'line1' => $line1,
                    'line2' => $line2,
                    'line3' => $line3,
                    'city' => $city,
                    'region' => $region,
                    'postalCode' => $postalCode,
                    'country' => $country
                ]
            ]
        ];

        // Put this line in the model
        array_push($this->_model['lines'], $l);
        $this->_line_number++;

        // Continue building
        return $this;
    }

    /**
     * Add a line with an exemption to this transaction
     *
     * @param   float               $amount         The amount of this line item
     * @param   string              $exemptionCode  The exemption code for this line item
     * @param   string              $lineNumber     Custom Line number, defaults to auto-incremented number if null
     * @return  TransactionBuilder
     */
    public function withExemptLine($amount, $itemCode, $exemptionCode, $lineNumber = null)
    {
        if($lineNumber === null) {
            $lineNumber = $this->_line_number;
        }
        $l = [
            'number' => $lineNumber,
            'quantity' => 1,
            'amount' => $amount,
            'exemptionCode' => $exemptionCode,
            'itemCode'      => $itemCode
        ];
        array_push($this->_model['lines'], $l);
        $this->_line_number++;

        // Continue building
        return $this;
    }

    /**
     * Add a exemption number for this document
     *
     * @param   string              $exemptionNo     Exemption Number for this document.
     * @return  TransactionBuilder
     */
    public function withExemptionNo($exemptionNo)
    {
        $this->_model['exemptionNo'] = $exemptionNo;
        return $this;
    }

    /**
     * Specific a currency code for this transaction
     *
     * @param   string              $currencyCode Specific the three-character ISO 4217 code that is being used for this transaction (optional)
     * @return  TransactionBuilder
     */
    public function withCurrencyCode($currencyCode)
    {

        $this->_model['currencyCode'] = $currencyCode;
        return $this;
    }

    /**
     * Checks to see if the current model has a line.
     *
     * @return  int
     */
    private function getMostRecentLineIndex()
    {
        $c = count($this->_model['lines']);
        if ($c <= 0) {
            throw new \Exception("No lines have been added. The memberName method applies to the most recent line.  To use this function, first add a line.");
        }

        return $c-1;
    }

    /**
     * Get the line number of the most recently added line
     *
     * @return int|null
     */
    public function getCurrentLineNumber()
    {
        try {
            $li = $this->getMostRecentLineIndex();
            return $this->_model['lines'][$li]['number'];
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * Create this transaction
     *
     * @param string $include Specifies objects to include in the response after transaction is created
     * @return  TransactionModel
     */
    public function create($include = null)
    {
        return $this->_client->createTransaction($include, $this->_model);
    }

    /**
     * Create this transaction, or adjust an existing transaction that has the same transaction code.
     *
     * @param string $include
     * @return TransactionModel
     */
    public function createOrAdjust($include = null)
    {
        $createOrAdjustTransactionModel = new CreateOrAdjustTransactionModel();
        $createOrAdjustTransactionModel->createTransactionModel = $this->_model;
        return $this->_client->createOrAdjustTransaction($include, $createOrAdjustTransactionModel);
    }

    /**
     * Create a transaction adjustment request that can be used with the AdjustTransaction() API call
     *
     * @return  AdjustTransactionModel
     */
    public function createAdjustmentRequest($desc, $reason)
    {
        return [
            'newTransaction' => $this->_model,
            'adjustmentDescription' => $desc,
            'adjustmentReason' => $reason
        ];
    }
    
    /**
     * Add Avalara LineItemModel objects to the lines array
     *
     * @return  TransactionBuilder
     */
    public function withLineItem($thing)
    {
        $mode = is_array($thing) ? 'multi' : 'single';

        if ($mode === 'single') {
            $this->_model['lines'][] = (array)$thing;
            $this->_line_number++;
        }


        if ($mode === 'multi') {
            foreach($thing as $lineItem) {
                $this->_model['lines'][] = (array)$lineItem;
                $this->_line_number++;
            }
        }

        return $this;
    }    
}
?>
