<?php

namespace Velis\Bpm;

use Exception;
use Velis\Acl\Role;
use Velis\App;
use Velis\Arrays;
use Velis\Bpm\Company\Address;
use Velis\Bpm\Company\Type;
use Velis\Bpm\Ticket\Category;
use Velis\Exception\BusinessLogicException;
use Velis\Filter;
use Velis\Lang;
use Velis\Model\DataObject;
use Velis\Model\Routable;
use Velis\Model\Sanitizable;
use Velis\Notification\Recipient;
use Velis\ParameterBag;

/**
 * Company model
 * @author Olek Procki <olo@velis.pl>
 */
class Company extends DataObject implements Sanitizable, Recipient, Routable
{
    /**
     * List sort options
     */
    const ORDER_NAME          = 'name';
    const ORDER_ID            = 'company_id DESC';


    protected static $_listDefaultOrder = self::ORDER_NAME;
    protected static $_filterListParams = true;


    /**
     * Registered users
     * @var \Velis\Bpm\User
     */
    protected $_users;


    /**
     * Addresses list
     * @var \Velis\Bpm\Company\Address[]
     */
    protected $_addresses;


    /**
     * Company employees list
     * @var \Velis\Bpm\Company\Person
     */
    protected $_employees;


    /**
     * Buffered project list
     * @var \Velis\Bpm\Project[]
     */
    protected $_projects;



    /**
     * Returns related sql table
     * @return string
     */
    protected function _getTableName()
    {
        return 'app.company_tab';
    }


    /**
     * Returns view for selecting
     * @return string
     */
    protected function _getListDatasource()
    {
        return 'app.company c';
    }


    /**
     * Returns company name
     * @return string
     */
    public function getName()
    {
        if ($this->short_name) {
            return $this->short_name;
        } else {
            return $this->name;
        }
    }


    /**
     * Returns company email
     * @return string
     */
    public function getEmail()
    {
        return $this->email_address;
    }


    /**
     * Returns true if active
     * @return bool
     */
    public function isActive()
    {
        return $this['active'] == 1;
    }


    /**
     * Returns true if key account
     * @return bool
     */
    public function isKeyAccount()
    {
        return $this['key_account'] == 1;
    }


    /**
     * Returns rewrite route name
     * @return string
     */
    public function getRouteName()
    {
        return 'company';
    }


    /**
     * Returns standard url with no rewrite
     * @return string
     */
    public function getStandardUrl()
    {
        return '/company/index?' . $this->_getPrimaryKeyField() . '=' . $this->id();
    }


    /**
     * Returns edit form URL
     * @return string
     */
    public function getEditUrl()
    {
        return App::getRouteUrl('company-edit', $this->_getPrimaryKeyParam());
    }


    /**
     * Returns company types
     * @return \Velis\Bpm\Company\Type[]
     */
    public function getTypes()
    {
        if (!$this->offsetExists('company_types')) {
            $this->append(static::instance($this->id(), true));
        }
        return Type::get(explode(',', $this['company_types']));
    }


    /**
     * Checks if company has $type
     *
     * @param \Velis\Bpm\Company\Type|string $type
     * @return bool
     */
    public function hasType($type)
    {
        if (!$type instanceof Type) {
            $type = new Type($type);
        }
        return $type->belongs($this->getTypes());
    }


    /**
     * Returns available roles for user in company
     * @return \Velis\Acl\Role[]
     */
    public function getUserAvailableRoles()
    {
        return Role::get(Arrays::getColumn($this->getTypes(), 'role_id'));
    }


    /**
     * Get customer name for search combo
     * @return string
     */
    public function getSearchName()
    {
        if (!isset($this->searchName)) {
            $searchName = $this->isKeyAccount() ? 'VIP ' : '';
            $this->searchName = $searchName . $this->getName();
        }
        return $this->searchName;
    }


    /**
     * Returns list of addressed
     * @return \Velis\Bpm\Company\Address[]
     */
    public function getAddresses()
    {
        $addressClass = class_exists('\Company\Address') ? '\Company\Address' : '\Velis\Bpm\Company\Address';

        if (!isset($this->_addresses)) {
            $this->_addresses = array();
            foreach ($addressClass::listAll($this->_getPrimaryKeyParam(), 'address_no') as $address) {
                $this->_addresses[$address->address_no] = $address;
            }
        }
        return $this->_addresses;
    }


    /**
     * Returns default company address
     * @return \Velis\Bpm\Company\Address
     */
    public function getMainAddress()
    {
        $addresses = $this->getAddresses();
        return reset($addresses);
    }


    /**
     * Returns default customer address
     * @return \Velis\Bpm\Company\Address
     */
    public function getCorrespondenceAddress()
    {
        $addresses = $this->getAddresses();
        foreach ($addresses as $address) {
            if ($address['is_correspondence_address']) {
                return $address;
            }
        }

        return $this->getMainAddress();
    }


    /**
     * Returns company persons full list
     *
     * @param bool $withDependentPersons
     * @param bool $reload
     *
     * @return \Velis\Bpm\Company\Person[]
     */
    public function getPersons($withDependentPersons = true, $reload = false)
    {
        if (isset($this->_employees) && !$reload) {
            return $this->_employees;
        }

        $params = array();

        if (!$withDependentPersons) {
            $params['root_only'] = true;
        }
        if (class_exists('\Company\Person')) {
            $personClass = '\Company\Person';
        } else {
            $personClass = '\Velis\Bpm\Person';
        }

        $this->_employees = $personClass::byCompany($this, $params);
        return $this->_employees;
    }


    /**
     * Returns all company's projects
     * @return Project[]
     */
    public function getProjects($filter = null)
    {
        $params = $this->_getPrimaryKeyParam();

        if (!empty($filter) && is_array($filter)) {
            $params = $params + $filter;
        }

        if (!isset($this->_projects)) {
            $this->_projects = Project::listAll($params, 'name ASC');
            if (App::$config->settings->defaultProject) {
                $this->_projects[App::$config->settings->defaultProject] = Project::get(App::$config->settings->defaultProject);
            }
        }
        return $this->_projects;
    }


    /**
     * Returns selectable projects (subprojects by recursion)
     *
     * @param bool $output
     * @return \Velis\Bpm\Project[]
     */
    public function getSelectableProjects($output = false)
    {
        $projects = array();

        foreach ($this->getProjects() as $project) {
            if ($project->isActive()) {
                $projects[] = $project;
            }
        }

        $selectableProjects = array();
        if (sizeof($projects)) {
            $selectableProjects = Project::getNestedList(
                array ('active' => 1),
                $projects
            );
        }

        if ($output) {
            foreach ($selectableProjects as $projectId => $project) {
                $project['output'] = $project->output();
            }
        }

        return $selectableProjects;
    }


    /**
     * Returns selectable categories
     *
     * @param bool $output
     * @return Velis_Helpdesk_Ticket_Category[]
     */
    public function getSelectableCategories($output = false)
    {
        $projects = $this->getProjects();
        Project::loadProjectsCategories($projects);

        $categories = array();
        foreach ($projects as $project) {
            foreach ($project->getCategories() as $category) {
                if ($category->isActive()) {
                    $categories[$category->id()] = $category;
                }
            }
        }

        $selectableCategories = Category::getNestedList(
            array('active' => 1),
            $categories
        );

        if ($output) {
            foreach ($selectableCategories as $categoryId => $category) {
                $category['output'] = $category->output();
            }
        }

        return $selectableCategories;
    }


    /**
     * Modifies company data
     *
     * @param bool $checkDiff
     * @return \Velis\Bpm\Company
     */
    public function modify($checkDiff = false)
    {
        $commit = self::$_db->startTrans();

        try {
            $types = $this['types'];
            parent::modify($checkDiff);

            if ($types) {
                $this->setTypes($types);
            }
            if ($commit) {
                self::$_db->commit();
            }

            return $this;
        } catch (\Exception $e) {
            if ($commit) {
                self::$_db->rollback();
            }

            if ($e->getCode() == 23505) {
                throw new \RuntimeException(Lang::get('GENERAL_VAT_NUMBER_EXISTS'), $e->getCode(), $e);
            } elseif ($e->getCode() == 23514) {
                throw new \RuntimeException(Lang::get('COMPANY_MAY_BY_JUST_CUSTOMER_OR_TENENT'), $e->getCode(), $e);
            } else {
                throw $e;
            }
        }
    }


    /**
     * Adds company data
     * @return \Velis\Bpm\Company
     */
    public function add($updateObjectId = true)
    {
        $commit = self::$_db->startTrans();

        try {
            $types = $this['types'];
            parent::add($updateObjectId);

            if ($types) {
                $this->setTypes($types);
            }

            if ($commit) {
                self::$_db->commit();
            }

            return $this;
        } catch (\Exception $e) {
            if ($commit) {
                self::$_db->rollback();
            }

            if ($e->getCode() == 23505) {
                throw new \RuntimeException(Lang::get('GENERAL_VAT_NUMBER_EXISTS'), $e->getCode(), $e);
            } elseif ($e->getCode() == 23514) {
                throw new \RuntimeException(Lang::get('GENERAL_FILL_VAT_NUMBER_POLAND'), $e->getCode(), $e);
            } else {
                throw $e;
            }
        }
    }


    /**
     * Set company types
     * @param \Velis\Bpm\Company\Type[]|string[] $types
     * @throws BusinessLogicException
     */
    public function setTypes($types)
    {
        if (empty($types)) {
            throw new BusinessLogicException(Lang::get('COMPANY_CHOOSE_TYPE'));
        }

        $commit = self::$_db->startTrans();

        try {
            self::$_db->execDML(
                'DELETE FROM app.company_to_company_type_tab WHERE company_id=:company_id',
                $this->_getPrimaryKeyParam()
            );

            foreach ($types as $type) {
                self::$_db->insert(
                    'app.company_to_company_type_tab',
                    array (
                        'company_id' => $this->id(),
                        'company_type_id' => $type instanceof Type ? $type->id() : $type
                    )
                );
            }

            if ($commit) {
                self::$_db->commit();
            }
            return $this;
        } catch (Exception $e) {
            if ($commit) {
                self::$_db->rollback();
            }
            throw $e;
        }
    }


    /**
     * {@inheritDoc}
     */
    public static function getList($page = 1, $params = null, $order = self::ORDER_NAME, $limit = self::ITEMS_PER_PAGE, $fields = null)
    {
        $params = new ParameterBag($params);

        $instance = new static();
        $companyField = $instance->_getPrimaryKeyField();

        if ($params['project_id']) {
            self::$_listConditions[] = "EXISTS (
                SELECT 1 FROM app.project_" . str_replace('_id', '_tab', $companyField) . " p
                  WHERE p.project_id = :project_id
                    AND p.$companyField= c.$companyField
            )";
            self::$_listParams['project_id'] = $params['project_id'];
        }

        if ($params['vat_number']) {
            $params['vat_number'] = Filter::filterDigits($params['vat_number']);
        }

        if ($params['search']) {
            self::$_listConditions[] = "(name ILIKE :search OR short_name ILIKE :search)";

            $params['search'] = "%" . $params['search'] . "%";
            self::$_listParams['search'] = $params['search'];
        }

        if ($params['name']) {
            self::$_listConditions[] = "(c.name ILIKE :name OR c.short_name ILIKE :name)";
            self::$_listParams['name'] = '%' . $params['name'] . '%';
            unset($params['name']);
        }

        if ($params['company_type_id']) {
            if (!is_array($params['company_type_id'])) {
                $params['company_type_id'] = array($params['company_type_id']);
            }
            self::$_listConditions[] = "ARRAY['" . implode("','", $params['company_type_id']) . "']::varchar[] && c.company_types_array";
        }

        if ($params['city']) {
            if (class_exists('\Company\Address')) {
                $address = new \Company\Address();
            } else {
                $address = new Address();
            }

            self::$_listConditions[] = "EXISTS(
                SELECT 1 FROM " . $address->_getTableName() . " ca
                WHERE ca.$companyField = c.$companyField AND ca.city ILIKE :city)";

            self::$_listParams['city'] = $params['city'];
        }

        if (!$params['show_inactive']) {
            self::$_listConditions[] = "active = 1";
        }

        return parent::getList($page, $params, $order, $limit, $fields);
    }
}
