<?php

use EesyPHP\Log;
use Unidecode\Unidecode;

if (!isset($db_dsn)) {
  Log :: fatal('Database DSN not configured');
  exit(1);
}

try {
  $pdo = new PDO(
    $db_dsn,
    isset($db_user)?$db_user:null,
    isset($db_pwd)?$db_pwd:null,
    isset($db_options)?$db_options:null
  );
  $pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  $fpdo = new FluentPDO($pdo);

  $fpdo -> debug = function ($q) {
    $time  = sprintf('%0.3f', $q->getTime() * 1000) . ' ms';
    $rows  = ($q->getResult()) ? $q->getResult()->rowCount() : 0;
    $query = $q->getQuery();
    $msg = "# DB query ($time; rows = $rows) : $query";

    $parameters = $q->getParameters();
    if ($parameters) {
      if (is_array($parameters)) {
        $msg .= "\n# Parameters: '" . implode("', '", $parameters) . "'";
      }
      else {
        $msg .= "\n# Parameters: '" . vardump($parameters) . "'";
      }
    }

    Log :: debug($msg);
  };
}
catch(Exception $e) {
  Log :: error("Fail to connect to DB (DSN : '$db_dsn') : ".$e->getMessage());
  Log :: fatal(_('Unable to connect to the database.'));
}

/*
 * Handle date/datetime format
 */
function db_date2time($date) {
  global $db_date_format;
  setlocale(LC_TIME, "fr_FR");
  $pdate = strptime($date, $db_date_format);
  return mktime(
    $pdate['tm_hour'], $pdate['tm_min'], $pdate['tm_sec'],
    $pdate['tm_mon'] + 1, $pdate['tm_mday'], $pdate['tm_year'] + 1900
  );
}

function db_time2date($time) {
  global $db_date_format;
  setlocale(LC_TIME, "fr_FR");
  return strftime($db_date_format, $time);
}

function db_datetime2time($date) {
  global $db_datetime_format;
  setlocale(LC_TIME, "fr_FR");
  $pdate = strptime($date, $db_datetime_format);
  return mktime(
    $pdate['tm_hour'], $pdate['tm_min'], $pdate['tm_sec'],
    $pdate['tm_mon'] + 1, $pdate['tm_mday'], $pdate['tm_year'] + 1900
  );
}

function db_time2datetime($time) {
  global $db_datetime_format;
  setlocale(LC_TIME, "fr_FR");
  return strftime($db_datetime_format, $time);
}

/*
 * Helper to format row info from DB
 */
function _format_row_info($row, $datetime_fields) {
  // Convert datetime fields
  foreach($datetime_fields as $field)
    if ($row[$field])
      $row[$field] = db_datetime2time($row[$field]);
  return $row;
}

/*
 * Methods to handle items
 */
function get_items($orderby='id', $raw_values=false) {
  global $fpdo;
  try {
    $query = $fpdo  -> from('item')
                    -> orderBy($orderby);

    $result = $query -> execute();
    if ($result !== false) {
      $info = $result -> fetchAll();
      if ($info === false)
        return null;
      if ($raw_values)
        return $info;

      $items = array();
      foreach ($info as $item)
        $items[$item['id']] = _format_row_info($item, array('date'));
      return $items;
    }
  }
  catch (Exception $e) {
    Log :: error("Error retreiving items info from database : ".$e->getMessage());
  }
  return false;
}

function get_item($id, $raw_values=false) {
  global $fpdo;
  try {
    $query = $fpdo  -> from('item')
                    -> where('id', $id);

    $result = $query -> execute();
    if ($result !== false) {
      $info = $result -> fetch();
      if ($info === false)
        return null;
      if ($raw_values)
        return $info;

      return _format_row_info($info, array('date'));
    }
  }
  catch (Exception $e) {
    Log :: error("Error retreiving item #$id info from database : ".$e->getMessage());
  }
  return false;
}

function add_item($values) {
  global $fpdo;
  $values['date'] = db_time2datetime(time());
  try {
    $result = $fpdo -> insertInto('item')
        -> values($values)
        -> execute();

    if ($result !== false) {
      $item = get_item($result);
      Log :: info("New item #$result added");
      trigger_hook('item_added', $item);
      return $item;
    }
  }
  catch (Exception $e) {
    Log :: error("Error creating item in database : ".$e->getMessage());
  }
  return false;
}

function update_item($id, $changes) {
  global $fpdo;

  if (!is_array($changes))
    return false;
  if (empty($changes))
    return true;
  $item = get_item($id, true);
  if (!is_array($item)) return false;

  if (isset($changes['date']) && !is_null($changes['date']))
    $changes['date'] = db_time2datetime($changes['date']);

  try {
    $result = $fpdo -> update('item')
                    -> set($changes)
                    -> where('id', $id)
                    -> execute();

    if ($result !== false) {
      Log :: info("Item #$id updated");
      trigger_hook('item_updated', $item);
      return true;
    }
  }
  catch (Exception $e) {
    Log :: error("Error updating item #$id in database : ".$e->getMessage());
  }
  return false;
}

function change_item_status($id, $status) {
  if (update_item($id, array('status' => $status))) {
    Log :: info("Status of item #$id changed to $status.");
    return true;
  }
  return false;
}

function archive_item($id) {
  return change_item_status($id, 'archived');
}

function delete_item($id) {
  global $fpdo;
  try {
    $result = $fpdo -> deleteFrom('item')
                    -> where('id', $id)
                    -> execute();

    if ($result !== false) {
      Log :: info("Item #$id deleted");
      return True;
    }
  }
  catch (Exception $e) {
    Log :: error("Error deleting item #$id from database : ".$e->getMessage());
  }
  return false;
}

function search_items($params) {
  global $fpdo, $db_dsn;

  // Detect PgSQL backend
  $is_pgsql = (strpos($db_dsn, "pgsql:") === 0);

  $where = array();
  if (isset($params['status']) && $params['status'] && $params['status'] != 'all')
     $where['status'] = $params['status'];

  $patterns_where = array();
  if (isset($params['pattern']) && $params['pattern']) {
    foreach(preg_split('/\s+/', trim($params['pattern'])) as $word) {
      if (!$word) continue;
      $patterns_word=array();

      // If numeric pattern ?
      if (preg_match('/^[0-9]+$/', $word)) {
        // Numeric pattern
        $word = intval($word);
        $patterns_word['id = ?'] = $word;
      }
      else {
        // Text pattern
        foreach (array('name', 'description') as $field) {
          if ($is_pgsql) {
            $word = Unidecode::unidecode($word);
            $patterns_word["unaccent($field) ILIKE ?"] = "%$word%";
          }
          else
            $patterns_word["$field LIKE ?"] = "%$word%";
        }
      }
      $patterns_where[] = $patterns_word;
    }
  }

  $order='id';
  $orders=array('id', 'name', 'date', 'status', 'description');
  if (isset($params['order'])) {
     if (!in_array($order, $orders))
        return -1;
     $order=$params['order'];
  }

  $order_direction='DESC';
  if (isset($params['order_direction']) && $params['order_direction']) {
     if (!in_array($params['order_direction'], array('ASC', 'DESC')))
        return -1;
     $order_direction=$params['order_direction'];
  }

  $orderby="$order $order_direction";

  $limit = "";
  $page = 1;
  $nb_by_page = 10;
  $offset = 0;
  if (!isset($params['all'])) {
     if (isset($params['page']) && $params['page']>0) {
        if (isset($params['nb_by_page']) && $params['nb_by_page']>0) {
           $nb_by_page = intval($params['nb_by_page']);
        }
        $page = intval($params['page']);
     }
     $offset = ($page-1)*$nb_by_page;
     $limit = $nb_by_page;
  }

  try {
    $query = $fpdo -> from('item');
    if (!empty($where))
      $query -> where($where);
    foreach ($patterns_where as $patterns_word)
      call_user_func_array(
        array($query, 'where'),
        array_merge(
          array('('.implode(' OR ', array_keys($patterns_word)).')'),
          array_values($patterns_word)
        )
      );
    $result = $query  -> orderBy($orderby)
          -> limit($limit)
          -> offset($offset)
          -> execute();

    if ($result === false) {
      Log :: error('search_items() : search in DB return false');
      return false;
    }

    $rows = $result -> fetchAll();
    $items = array();
    foreach ($rows as $row) {
      $items[] = _format_row_info($row, array('date'));
    }
    if (isset($params['all'])) {
      return array(
        'count'    => count($items),
        'first'    => 1,
        'last'     => count($items),
        'nb_pages' => 1,
        'page'     => 1,
        'items'    => $items
      );
    }
    $query_count = $fpdo  -> from('item')
          -> select(null)
          -> select('count(*) as count');
    if (!empty($where))
      $query_count -> where($where);
      foreach ($patterns_where as $patterns_word)
        call_user_func_array(
          array($query_count, 'where'),
          array_merge(
            array('('.implode(' OR ', array_keys($patterns_word)).')'),
            array_values($patterns_word)
          )
        );

    $result_count = $query_count -> execute();

    if ($result_count === false) {
      Log :: debug('search_items() : search for count in DB return false');
      return False;
    }
    $count = $result_count -> fetch();
    return array(
      'count'     => $count['count'],
      'first'     => $offset+1,
      'last'      => (
        $offset+$nb_by_page<$count['count']?
        $offset+$nb_by_page:$count['count']),
      'nb_pages'  => ceil($count['count']/$nb_by_page),
      'page'      => $page,
      'items'     => $items,
    );
  }
  catch (Exception $e) {
    Log :: exception(
      $e, "An exception occured searching items with params %s infos from database : ",
      preg_replace("/\n[ \t]*/", " ", print_r($params, true))
    );
  }
  return false;
}

function export_items($fd=null) {
  if (!$fd) $fd = fopen('php://output', 'w');
  fputcsv(
    $fd,
    array (
      'id',
      'name',
      'date',
      'status',
      'description',
    )
  );
  $items = get_items();
  foreach($items as $item) {
    fputcsv(
      $fd,
      array(
        $item['id'],
        $item['name'],
        $item['date'],
        $item['status'],
        $item['description'],
      )
    );
  }
  return True;
}

function restore_items($fd=null) {
  global $fpdo;
  if (!$fd) $fd = fopen('php://stdin', 'r');
  try {
    $result = $fpdo -> deleteFrom('item')
        -> execute();
    if ($result === false) {
      Log :: error("An unknown error occured truncating item table in database.");
      return false;
    }
  }
  catch (Exception $e) {
    Log :: error("Error truncating item table in database : ".$e->getMessage());
    return false;
  }

  $first_row = false;
  $line = 0;
  $restored = 0;
  $error = false;
  $datetime_fields = array (
    'date',
  );
  // Map fields to support hold CSV files format
  $mapping = array (
    'creation_date'  => 'date',
    'desc'           => 'description',
  );

  while (($row = fgetcsv($fd)) !== FALSE) {
    $line++;
    if ($first_row === false) {
      $first_row = $row;
      continue;
    }
    try {
      $values = array();
      for ($i=0; $i < count($first_row); $i++) {
        if (!$row[$i]) continue;
        $field = (
          array_key_exists($first_row[$i], $mapping)?
          $mapping[$first_row[$i]]:
          $first_row[$i]);
        $value = (in_array($field, $datetime_fields)?db_time2datetime($row[$i]):$row[$i]);
        $values[$field] = $value;
      }
      $result = $fpdo -> insertInto('item')
          -> values($values)
          -> execute();

      if ($result !== false) {
        $restored++;
      }
      else {
        Log :: error("Unkwown error occured restoring item from line #$line :\n".print_r($values, true));
        $error = true;
      }
    }
    catch (Exception $e) {
      Log :: error(
        "Error restoring item from line #$line : ".$e->getMessage()."\n".print_r($values, true));
      $error = true;
    }
  }
  Log :: info("$restored items restored");

  // Trigger hooks
  trigger_hook('items_restored');

  return !$error;
}

# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab