<?php
class Product extends Entity {
    private $id = 0;
    private $brand = '';
    private $name = '';
    private $invoice = '';
    private $serial = '';
    private $createtime = null;
    private $discardtime = null;
    private $info = array();
    private $tags = array();
    
    public static function create_product(
        $brand,
        $name,
        $invoice,
        $serial,
        $info = array(),
        $tags = array()
    ) {
        $now = time();
        begin_trans();
        try {
            $stmt = 'insert into `product`
                         (`brand`, `name`, `invoice`, `serial`, `createtime`)
                         values (?, ?, ?, ?, ?)';
            $ins_prod = prepare($stmt);
            bind($ins_prod, 'ssssi', $brand, $name, $invoice, $serial, $now);
            execute($ins_prod);
            $product = new Product($serial, 'serial');
            foreach($info as $field => $value) {
                $product->set_info($field, $value);
            }
            foreach($tags as $tag) {
                $product->add_tag($tag);
            }
            commit_trans();
            return $product;
        } catch(Exception $e) {
            revert_trans();
            throw $e;
        }
    }
    
    public function __construct($clue, $type = 'id') {
        parent::__construct();
        $search = null;
        switch($type) {
            case 'id':
                $search = prepare('select `id` from `product`
                                   where `id`=?');
                bind($search, 'i', $clue);
                break;
            case 'serial':
                $search = prepare('select `id` from `product`
                                   where `serial`=?');
                bind($search, 's', $clue);
                break;
            default:
                throw new Exception('Invalid type.');
        }
        execute($search);
        $result = result_single($search);
        if($result === null) {
            throw new Exception('Product does not exist.');
        }
        $this->id = $result['id'];
        $this->update_fields();
        $this->update_info();
        $this->update_tags();

        # Global variables are bad, but passing these email properties
        # around everywhere would be worse
        global $sender, $notify_discard, $email_subject_prefix;
        $this->discard_email_address = $notify_discard;
        $this->email_sender = $sender;
        $this->email_subject_prefix = $email_subject_prefix;
    }
    
    private function update_fields() {
        $get = prepare('select * from `product` where `id`=?');
        bind($get, 'i', $this->id);
        execute($get);
        $product = result_single($get);
        $this->brand = $product['brand'];
        $this->name = $product['name'];
        $this->invoice = $product['invoice'];
        $this->serial = $product['serial'];
        $this->createtime = $product['createtime'];
        $this->discardtime = $product['discardtime'];
        return true;
    }
    
    private function update_info() {
        $get = prepare('select * from `product_info`
                        where `product`=? order by `field`');
        bind($get, 'i', $this->id);
        execute($get);
        foreach(result_list($get) as $row) {
            $field = $row['field'];
            $data = $row['data'];
            $this->info[$field] = $data;
        }
        return true;
    }
    
    private function update_tags() {
        $get = prepare('select * from `product_tag`
                        where `product`=? order by `tag`');
        bind($get, 'i', $this->id);
        execute($get);
        $newtags = array();
        foreach(result_list($get) as $row) {
            $newtags[] = $row['tag'];
        }
        $this->tags = $newtags;
        return true;
    }

    /*
      Return a list of field-value mappings containing all matching search terms.
     */
    public function matches($terms, $ldap) {
        $matches = array();

        // Create a list mapping all basic fields to getters
        $fields = array('brand' => 'get_brand',
                        'name' => 'get_name',
                        'invoice' => 'get_invoice',
                        'serial' => 'get_serial',
                        'status' => 'get_status');

        foreach($terms as $term) {
            $key = $term->get_key();
            $matched = false;
            switch($key) {
                case 'brand':
                case 'name':
                case 'invoice':
                case 'serial':
                case 'status':
                    // If $key is a standard field, check against its value
                    $getter = $fields[$key];
                    $value = $this->$getter();
                    if(match($term, $value)) {
                        //Record a successful match
                        $matches[$key] = $value;
                        $matched = true;
                    }
                    break;
                case 'tag':
                    // If $key is tag, iterate over the tags
                    $matched_tags = $this->match_tags($term);
                    if($matched_tags) {
                        // Record a successful match
                        $matched = true;
                        if(!isset($matches['tags'])) {
                            // This is the first list of matching tags
                            $matches['tags'] = $matched_tags;
                        } else {
                            // Merge these results with existing results
                            $matches['tags'] = array_unique(
                                array_merge($matches['tags'],
                                            $matched_tags));
                        }
                    }
                    break;
                case 'fritext':
                    // if $key is fritext:
                    // First check basic fields
                    foreach($fields as $field => $getter) {
                        $value = $this->$getter();
                        if(match($term, $value)) {
                            $matches[$field] = $value;
                            $matched = true;
                        }
                    }
                    // Then tags
                    $matched_tags = $this->match_tags($term);
                    if($matched_tags) {
                        $matched = true;
                        if(!isset($matches['tags'])) {
                            $matches['tags'] = $matched_tags;
                        } else {
                            $matches['tags'] = array_unique(
                                array_merge($matches['tags'],
                                            $matched_tags));
                        }
                    }
                    // Then custom fields
                    foreach($this->get_info() as $field => $value) {
                        if(match($term, $value)) {
                            //Record a successful match
                            $matches[$field] = $value;
                            $matched = true;
                        }
                    }
                    break;
                default:
                    // Handle custom fields
                    $info = $this->get_info();
                    if(isset($info[$key])) {
                        // If $key is a valid custom field on this product
                        $value = $info[$key];
                        if(match($term, $value)) {
                            //Record a successful match
                            $matches[$key] = $value;
                            $matched = true;
                        }
                    }
                    break;
            }
            // If a mandatory match failed, the entire search has failed
            // and we return an empty result.
            if($term->is_mandatory() && !$matched) {
                return array();
            }
            // If a negative match succeeded, the entire search has failed
            // and we return an empty result.
            if($term->is_negative() && $matched) {
                return array();
            }
        }
        if($matchAll && array_diff_assoc($terms, $matches)) {
            return array();
        }
        return $matches;
    }

    private function match_tags($term) {
        $tags = $this->get_tags();
        $matches = array();
        foreach($tags as $tag) {
            if(match($term, $tag)) {
                $matches[] = $tag;
            }
        }
        return $matches;
    }

    public function get_label($name) {
        switch($name) {
            case 'brand':
                return 'Tillverkare';
                break;
            case 'name':
                return 'Namn';
                break;
            case 'invoice':
                return 'Fakturanummer';
                break;
            case 'serial':
                return 'Serienummer';
                break;
            case 'tags':
                return 'Taggar';
                break;
            default:
                return ucfirst($name);
                break;
        }
    }

    public function get_id() {
        return $this->id;
    }

    public function get_createtime() {
        return $this->createtime;
    }

    public function get_discardtime() {
        return $this->discardtime;
    }

    public function discard() {
        if($this->get_status() != 'available') {
            return false;
        }
        $now = time();
        $update = prepare('update `product` set `discardtime`=? where `id`=?');
        bind($update, 'ii', $now, $this->id);
        execute($update);
        $this->discardtime = $now;
        if($this->discard_email_address) {
            $this->send_discard_email();
        }
        return true;
    }

    private function send_discard_email() {
        $brand = $this->brand;
        $name = $this->name;
        $invoice = $this->invoice;
        $serial = $this->serial;
        $discardtime = format_date($this->discardtime);

        $subject = $this->email_subject_prefix.$brand.' '.$name.' skrotad';
        $message = <<<EOF
Hej!

Följande artikel har skrotats i Boka:

$brand $name, serienummer: $serial, fakturanummer: $invoice

EOF;
        try {
            mb_send_mail($this->discard_email_address,
                         $subject,
                         $message,
                         'From: '.$this->email_sender);
        } catch(Exception $e) {
            error_log($e->getMessage());
            mb_send_mail($this->error,
                         "Kunde inte skicka mail",
                         "Mail kunde inte skickas till "
                          . $this->discard_email_address);
        }
    }

    public function toggle_service() {
        $status = $this->get_status();
        $now = time();
        $update = '';
        if($status == 'service') {
            return $this->get_active_service()->end();
        } else if($status == 'available') {
            Service::create_service($this);
            return true;
        }
        $id = $this->get_id();
        throw new Exception("The state ($status) of this product (id $id) "
                           ."does not allow servicing.");
    }

    public function get_active_service() {
        $find = prepare("select `id` from `event` where
                             `type`='service'
                             and `returntime` is null
                             and product=?");
        bind($find, 'i', $this->id);
        execute($find);
        $result = result_single($find);
        if($result === null) {
            return null;
        }
        return new Service($result['id']);
    }
    
    public function get_brand() {
        return $this->brand;
    }
    
    public function set_brand($newbrand) {
        $update = prepare('update `product` set `brand`=? where `id`=?');
        bind($update, 'si', $newbrand, $this->id);
        execute($update);
        $this->brand = $newbrand;
        return true;
    }
    
    public function get_name() {
        return $this->name;
    }
    
    public function set_name($newname) {
        $update = prepare('update `product` set `name`=? where `id`=?');
        bind($update, 'si', $newname, $this->id);
        execute($update);
        $this->name = $newname;
        return true;
    }
    
    public function get_invoice() {
        return $this->invoice;
    }
    
    public function set_invoice($newinvoice) {
        $update = prepare('update `product` set `invoice`=? where `id`=?');
        bind($update, 'si', $newinvoice, $this->id);
        execute($update);
        $this->invoice = $newinvoice;
        return true;
    }
    
    public function get_serial() {
        return $this->serial;
    }
    
    public function set_serial($newserial) {
        $update = prepare('update `product` set `serial`=? where `id`=?');
        bind($update, 'si', $newserial, $this->id);
        execute($update);
        $this->serial = $newserial;
        return true;
    }
    
    public function get_info() {
        return $this->info;
    }
    
    public function set_info($field, $value) {
        if(!$value) {
            return true;
        }
        $find = prepare('select * from `product_info`
                         where `product`=? and `field`=?');
        bind($find, 'is', $this->id, $field);
        execute($find);
        if(result_single($find) === null) {
            $update = prepare('insert into
                                   `product_info`(`data`, `product`, `field`)
                                   values (?, ?, ?)');
        } else {
            $update = prepare('update `product_info` set `data`=?
                               where `product`=? and `field`=?');
        }
        bind($update, 'sis', $value, $this->id, $field);
        execute($update);
        $this->update_info();
        return true;
    }
    
    public function remove_info($field) {
        $find = prepare('select * from `product_info`
                         where `product`=? and `field`=?');
        bind($find, 'is', $this->id, $field);
        execute($find);
        if(result_single($find) === null) {
            return true;
        }
        $update = prepare('delete from `product_info`
                           where `field`=? and `product`=?');
        bind($update, 'si', $field, $this->id);
        execute($update);
        $this->update_info();
        return true;
    }
    
    public function get_tags() {
        return $this->tags;
    }
    
    public function add_tag($tag) {
        if(!$tag) {
            return true;
        }
        $find = prepare('select * from `product_tag`
                         where `product`=? and `tag`=?');
        bind($find, 'is', $this->id, $tag);
        execute($find);
        if(result_single($find) === null) {
            $update = prepare('insert into `product_tag`(`tag`, `product`)
                                   values (?, ?)');
            bind($update, 'si', $tag, $this->id);
            execute($update);
            $this->update_tags();
        }
        return true;
    }
    
    public function remove_tag($tag) {
        $find = prepare('select * from `product_tag`
                         where `product`=? and `tag`=?');
        bind($find, 'is', $this->id, $tag);
        execute($find);
        if(result_single($find) === null) {
            return true;
        }
        $update = prepare('delete from `product_tag`
                           where `tag`=? and `product`=?');
        bind($update, 'si', $tag, $this->id);
        execute($update);
        $this->update_tags();
        return true;
    }

    public function get_status() {
        if($this->get_discardtime()) {
            return 'discarded';
        }
        if($this->get_active_service()) {
            return 'service';
        }
        $loan = $this->get_active_loan();
        if(!$loan) {
            return 'available';
        }
        if($loan->is_overdue()) {
            return 'overdue';
        }
        return 'on_loan';
    }

    public function get_historic_event($time) {
        $search = prepare("select `id`,`type` from `event`
                               where `product` = ?
                               and `starttime` < ?
                               and ( `returntime` > ?
                               or `returntime` is null )");
        bind($search, 'iii', $this->id, $time, $time);
        execute($search);
        $result = result_single($search);
        if(!$result) {
            return null;
        }
        $id = $result['id'];
        $type = $result['type'];
        switch($type) {
            case 'service':
                return new Service($id);
                break;
            case 'loan':
                return new Loan($id);
                break;
            default:
                throw new Exception("Invalid type '$type'");
        }
    }

    public function get_active_loan() {
        $find = prepare("select `id` from `event`
                             where `type`='loan'
                             and `returntime` is null
                             and product=?");
        bind($find, 'i', $this->id);
        execute($find);
        $result = result_single($find);
        if($result === null) {
            return null;
        }
        return new Loan($result['id']);
    }

    public function get_history() {
        $out = array();
        $find = prepare('select `id`,`type` from `event` '
                       .'where `product`=? order by `starttime` desc');
        bind($find, 'i', $this->id);
        execute($find);
        $items = result_list($find);
        foreach($items as $item) {
            $id = $item['id'];
            switch($item['type']) {
                case 'service':
                    $out[] = new Service($id);
                    break;
                case 'loan':
                    $out[] = new Loan($id);
                    break;
                default:
                    $type = $item['type'];
                    throw new Exception("Invalid type '$type'");
            }
        }
        return $out;
    }

    public function get_attachments() {
        $out = array();
        $find = prepare('select `id` from `attachment`
                         where `product`=? and `deletetime` is NULL
                             order by `uploadtime` asc');
        bind($find, 'i', $this->id);
        execute($find);
        $items = result_list($find);
        foreach($items as $item) {
            $out[] = new Attachment($item['id']);
        }
        return $out;
    }
}
?>