<?php
/**
 * This file is part of the {@link http://ontowiki.net OntoWiki} project.
 *
 * @copyright Copyright (c) 2011-2016, {@link http://aksw.org AKSW}
 * @license   http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
 */

/**
 * The main controller class for the datagathering component. This class
 * provides services for URI search, statement import and statement sync.
 *
 * @category   OntoWiki
 * @package    Extensions_Datagathering
 * @author     Philipp Frischmuth <pfrischmuth@googlemail.com>
 */
class DatagatheringController extends OntoWiki_Controller_Component
{
    // ------------------------------------------------------------------------
    // --- Search related constants -------------------------------------------
    // ------------------------------------------------------------------------

    const SEARCH_MODE_ALL        = 0;
    const SEARCH_MODE_PROPERTIES = 1;

    const SEARCH_DEFAULT_LIMIT = 20;
    const SEARCH_MAX_LIMIT     = 100;


    // ------------------------------------------------------------------------
    // --- Import and Sync related constants ----------------------------------
    // ------------------------------------------------------------------------

    /**
     * New versioning type codes.
     */
    const VERSIONING_IMPORT_ACTION_TYPE = 1010;
    const VERSIONING_SYNC_ACTION_TYPE   = 1020;

    /**
     * Syncing status codes
     */
    const NO_SYNC_CONFIG_FOUND = 20;
    const NO_EDITING_RIGHTS    = 10;
    const SYNC_SUCCESS         = 30;

    /**
     * IMPORT STATUS CODES
     */
    const IMPORT_OK                           = 10;
    const IMPORT_NO_DATA                      = 20;
    const IMPORT_NO_NEW_DATA                  = 21;
    const IMPORT_WRAPPER_ERR                  = 30;
    const IMPORT_WRAPPER_INSTANCIATION_ERR    = 40;
    const IMPORT_NOT_EDITABLE                 = 50;
    const IMPORT_WRAPPER_NOT_AVAILABLE        = 70;
    const IMPORT_WRAPPER_RESULT_NOT_AVAILABLE = 71;
    const IMPORT_CUSTOMFILTER_EXCEPTION       = 80;
    const IMPORT_GENERAL_EXCEPTION            = 90;

    // ------------------------------------------------------------------------
    // --- Import and Sync related private properties -------------------------
    // ------------------------------------------------------------------------

    /**
     * Contains an array with matching sync configs.
     *
     * @var array
     */
    private $_syncConfigCache = array();


    // ------------------------------------------------------------------------
    // --- General private properties for this component ----------------------
    // ------------------------------------------------------------------------

    /**
     * Contains the graph URI of the selected graph or the uri which is given
     * by the m parameter
     *
     * @var string|null
     */
    private $_graphUri = null;

    /**
     * Contains the properties as configured in the ini file.
     *
     * @var array
     */
    private $_properties = array();

    /**
     * Contains the sync model helper URI as configured in the ini file.
     *
     * @var string
     */
    private $_syncModelHelperBase = null;

    /**
     * Contains the sync model URI as configured in the ini file.
     *
     * @var string
     */
    private $_syncModelUri = null;

    /**
     * Contains a reference to the wrapper registry.
     *
     * @var Erfurt_Wrapper_Registry
     */
    private $_wrapperRegistry = null;


    // ------------------------------------------------------------------------
    // --- Component initialization -------------------------------------------
    // ------------------------------------------------------------------------

    public function init()
    {
        parent::init();

        $this->_syncModelUri        = $this->_privateConfig->syncModelUri;
        $this->_syncModelHelperBase = $this->_privateConfig->syncModelHelperBase;

        // For testability we reset the wrapper registry first, since
        // multiple calls of this method would fail otherwise.
        Erfurt_Wrapper_Registry::reset();
        $this->_wrapperRegisty = Erfurt_Wrapper_Registry::getInstance();

        $owApp = OntoWiki::getInstance();
        if (null !== $owApp->selectedModel) {
            $this->_graphUri = $owApp->selectedModel->getModelIri();
        } else {
            if (isset($this->_request->m)) {
                $this->_graphUri = $this->_request->m;
            }
        }
    }


    // ------------------------------------------------------------------------
    // --- URI and Property Search related methods ----------------------------
    // ------------------------------------------------------------------------

    /**
     * Searches for relevant URIs for given term(s). This service has two
     * modes. One that returns all matching URIs and one that returns only
     * resource URIs that are defined as properties or at least used once as
     * property.
     *
     * q - The mandatory parameter q contains a whitespace seperated list of
     *     serach terms.
     *
     * mode - This optional parameter sets the search mode. The default value
     *        is 0. In this mode all matching URIs will be returned. If the
     *        mode is set to 1, only matching URIs are returned, that are
     *        defined as properties or that are used as propertie at least
     *        once.
     *
     * limit - Optional sets the maximum number of URIs the result may contain.
     *         The default value is SEARCH_DEFAULT_LIMIT. The maximum value
     *         is SEARCH_MAX_LIMIT.
     *
     * classes - an optional paramter which is a json_encoded array of class URIs
     *
     * This service will output a json encoded string, which represents the
     * result list. Each entry is returned with an title, the URI itself and
     * the source of that result. The entry components are sepearted by a | and
     * the entries are seperated by a newline. If nothing is found an empty
     * json encodes string will be returned.
     */
    public function searchAction()
    {
        // Use the selected graph if present. If not, search all available graphs.
        if (null === $this->_graphUri) {
            throw new OntoWiki_Exception("model must be selected or specified with the m parameter");
        }
        $modelUri = $this->_graphUri;

        // Check for the mandatory q parameter.
        $q = $this->_request->q;
        if (null === $q) {
            $this->_response->setRawHeader('HTTP/1.0 400 Bad Request');
            echo '400 Bad Request - The q parameter is missing.';

            return;
        }
        $termsArray = explode(' ', $q);

        // check for the classes json array
        $classes = array();
        if (null !== $this->_request->classes) {
            $classes = json_decode($this->_request->classes);
            if (!is_array($classes)) {
                $classes = array();
            }
        }

        // Set the search mode.
        if (null !== $this->_request->mode) {
            if ((int)$this->_request->mode === self::SEARCH_MODE_PROPERTIES) {
                $mode = self::SEARCH_MODE_PROPERTIES;
            } else {
                // Default to search mode all.
                $mode = self::SEARCH_MODE_ALL;
            }
        } else {
            $mode = self::SEARCH_MODE_ALL;
        }

        // Check for optional limit parameter.
        if (null !== $this->_request->limit) {
            $limit = $this->_request->limit;
            if ((int)$limit > 0 && $limit <= self::SEARCH_MAX_LIMIT) {
                $limit = (int)$limit;
            } else {
                // Default to search mode all.
                $limit = self::SEARCH_DEFAULT_LIMIT;
            }
        } else {
            $limit = self::SEARCH_DEFAULT_LIMIT;
        }

        // Check whether given term is a URI! If a URI is given we return an empty result, for some users
        // may want to enter URIs themself.
        if (count($termsArray) === 1) {
            foreach ($termsArray as $t) {
                $regExp = '/^(' . implode(':|', $this->_listIanaSchemes()) . ':).$/';

                if (preg_match($regExp, $t)) {
                    echo json_encode('');

                    return;
                }
                if (strlen($t) > 20) {
                    echo json_encode('');

                    return;
                }
            }
        }

        $result = array();

        // Step 1a: Search the local database for URIs (class restricted)
        $localWithRestriction = $this->_searchLocal($termsArray, $modelUri, $mode, $limit, $classes);
        if (count($localWithRestriction) > 0) {
            $result = $localWithRestriction;
        }
        // Step 1b: Search the local database for URIs (NOT class restricted)
        $local = $this->_searchLocal($termsArray, $modelUri, $mode, $limit);
        if (count($local) > 0) {
            // put new result values at the end of the result array
            foreach ($local as $resultKey => $resultValue) {
                if (!isset($result[$resultKey])) {
                    $result[$resultKey] = $resultValue;
                }
            }
        }

        if ($mode === self::SEARCH_MODE_ALL && count($result) < $limit) {
            // Step 2: Extend the result with the results from plugins...
            $currentCount = count($result);

            require_once 'Erfurt/Event.php';
            $event             = new Erfurt_Event('onDatagatheringComponentSearch');
            $event->translate  = $this->_owApp->translate;
            $event->termsArray = $termsArray;
            $event->modelUri   = $modelUri;
            $event->classes    = $classes;
            $pluginResult      = $event->trigger();

            if (is_array($pluginResult)) {
                foreach ($pluginResult as $uri => $value) {
                    if (!isset($result[$uri])) {
                        $result[$uri] = $value;
                        $currentCount++;
                    }

                    if ($currentCount === $limit) {
                        break;
                    }
                }
            }

            // 3. Step: Expand qnames
            if (count($result) < $limit) {
                $currentCount = count($result);

                $expanded = $this->_expandNamespaces($termsArray, $modelUri);
                if (count($expanded) > 0) {
                    foreach ($expanded as $uri => $value) {
                        if (!isset($result[$uri])) {
                            $result[$uri] = $value;
                            $currentCount++;
                        }

                        if ($currentCount === $limit) {
                            break;
                        }
                    }
                }
            }

            // 4. If there is place for one more result, we add auto generated URI.
            if (count($result) < $limit) {
                $store = Erfurt_App::getInstance()->getStore();
                $model = $store->getModel($modelUri);

                if ($model) {
                    $suffix = '';
                    foreach ($termsArray as $t) {
                        $suffix .= ucfirst($t);
                    }

                    $prefix   = $model->getBaseUri();
                    $lastChar = substr($prefix, -1);
                    if ($lastChar !== '/' && $lastChar !== '#') {
                        $prefix .= '/';
                    }
                    $uri = $prefix . urlencode($suffix);

                    if (!isset($result[$uri])) {
                        $translate    = $this->_owApp->translate;
                        $result[$uri] = implode(' ', $termsArray) . '|' . $uri . '|' . $translate->_('Generated URI');
                    }
                }
            }
        }

        // 5. if the user input was an URI, give this URI as result back too
        if (Zend_Uri::check($termsArray[0]) == true) {
            $translate = $this->_owApp->translate;
            $result    = array(
                $termsArray[0] => $termsArray[0] . '|' . $termsArray[0] . '|' . $translate->_('your manual input')
            ) + $result;
        }

        $body = json_encode(implode(PHP_EOL, $result));

        if (isset($this->_request->callback)) {
            $callback = $this->_request->callback;

            // build jsonp
            $body = $callback
                . ' ('
                . $body
                . ')';
        }

        // send
        $response = $this->getResponse();
        $response->setBody($body);
    }

    /**
     * Expands qnames if possible.
     *
     * @param array  $termsArray
     * @param string $modelUri
     *
     * @return array
     */
    private function _expandNamespaces(array $termsArray, $modelUri)
    {
        $result = array();

        // We need the model here, for prefix definitions are model specific.
        if (null === $modelUri) {
            return $result;
        }

        $erfurtNamespaces = Erfurt_App::getInstance()->getNamespaces();
        $namespaces       = $erfurtNamespaces->getNamespacePrefixes($modelUri);

        $translate = $this->_owApp->translate;
        foreach ($termsArray as $t) {
            if ($pos = strpos($t, ':')) {
                $prefix = substr($t, 0, $pos);
                $local  = substr($t, $pos + 1);

                if (isset($namespaces[$prefix])) {
                    $uri          = $namespaces[$prefix] . $local;
                    $result[$uri] = implode(' ', $termsArray) . '|' . $uri . '|' . $translate->_('Expanded QNames');
                }
            }
        }

        return $result;
    }

    /**
     * Returns a number between 0 and 1, which weights a result regardings its
     * value for the given search.
     *
     * @param array       $termsArray
     * @param string      $uri
     * @param string|null $object
     *
     * @return int
     */
    private function _getWeight(array $termsArray, $uri, $object = null)
    {
        if (null !== $object) {
            $object = strtolower($object);
        }

        $weight = 0.0;

        if (substr($uri, -1) === '/') {
            $uri = substr($uri, 0, -1);
        }

        if ($i = strrpos($uri, '#')) {
            $uriLocalPart = substr($uri, $i + 1);
        } else {
            if ($i = strrpos($uri, '/')) {
                $uriLocalPart = substr($uri, $i + 1);
            }
        }

        if (null !== $uriLocalPart) {
            foreach ($termsArray as $t) {
                if (strpos($uriLocalPart, $t) !== false) {
                    $weight += 0.4 / count($termsArray);
                }
            }
        }

        if (null !== $object) {
            foreach ($termsArray as $t) {
                if (strpos($object, $t) !== false) {
                    $weight += 0.5 / count($termsArray);
                }
            }
        }

        if (strlen(implode('', $termsArray)) === strlen(str_replace(' ', '', trim($object)))) {
            $weight += 0.1;
        }

        return $weight;
    }

    /**
     * Returns a list of registered URI schemes.
     *
     * @return array
     */
    private function _listIanaSchemes()
    {
        return array('aaa', 'aaas', 'acap', 'cap', 'cid', 'crid', 'data', 'dav', 'dict', 'dns', 'fax', 'file', 'ftp',
                     'go', 'gopher', 'h323', 'http', 'https', 'iax', 'icap', 'im', 'imap', 'info', 'ipp', 'iris',
                     'iris.beep',
                     'iris.xpc', 'iris.xpcs', 'iris.lwz', 'ldap', 'mailto', 'mid', 'modem', 'msrp', 'msrps', 'mtqp',
                     'mupdate',
                     'news', 'nfs', 'nntp', 'opaquelocktoken', 'pop', 'pres', 'rtsp', 'service', 'shttp', 'sieve',
                     'sip', 'sips',
                     'snmp', 'soap.beep', 'soap.beeps', 'tag', 'tel', 'telnet', 'tftp', 'thismessage', 'tip', 'tv',
                     'urn',
                     'vemmi', 'xmlrpc.beep', 'xmlrpc.beeps', 'xmpp', 'z39.50r', 'z39.50s', 'afs', 'dtn', 'mailserver',
                     'pack',
                     'tn3270', 'prospero', 'snews', 'videotex', 'wais'
        );
    }

    /**
     * Searches the local database for URIs. If mode is set to properties only,
     * only defined properties and URIs that are used at least once as a
     * property are returned.
     *
     * the classes array is used for class restrictions
     *
     * @param array  $termsArray
     * @param string $modelUri
     * @param int    $mode
     * @param int    $limit
     * @param array  $classes
     *
     * @return array
     */
    private function _searchLocal(array $termsArray, $modelUri, $mode, $limit, $classes = array())
    {
        if ($mode === self::SEARCH_MODE_PROPERTIES) {
            return $this->_searchLocalPropertiesOnly($termsArray, $modelUri, $limit);
        }

        $store = Erfurt_App::getInstance()->getStore();

        // get a store specific text-search query2 object
        $searchPattern = $store->getSearchPattern(implode(" ", $termsArray), $modelUri);
        $query         = new Erfurt_Sparql_Query2();
        $query->addElements($searchPattern);
        $projVar = new Erfurt_Sparql_Query2_Var('resourceUri');
        $query->addProjectionVar($projVar);

        // add class restriction patterns for each class
        foreach ($classes as $class) {
            $classPattern = new Erfurt_Sparql_Query2_GroupGraphPattern();
            $classPattern->addTriple(
                $projVar,
                new Erfurt_Sparql_Query2_IriRef(EF_RDF_TYPE),
                new Erfurt_Sparql_Query2_IriRef($class)
            );
            $query->addElement($classPattern);
        }

        $query->setLimit(20);
        $query->setDistinct(true);

        $queryResult = $store->sparqlQuery($query, array('result_format' => 'extended'));

        $tempResult = array();

        foreach ($queryResult['results']['bindings'] as $row) {
            $object = isset($row['o']) ? $row['o']['value'] : null;

            $weight = $this->_getWeight($termsArray, $row['resourceUri']['value'], $object);

            if (isset($tempResult[$row['resourceUri']['value']])) {
                if ($weight > $tempResult[$row['resourceUri']['value']]) {
                    $tempResult[$row['resourceUri']['value']] = $weight;
                }
            } else {
                $tempResult[$row['resourceUri']['value']] = $weight;
            }
        }

        arsort($tempResult);

        require_once 'OntoWiki/Model/TitleHelper.php';
        require_once 'OntoWiki/Utils.php';
        if (null !== $modelUri) {
            $model       = $store->getModel($modelUri, false);
            $titleHelper = new OntoWiki_Model_TitleHelper($model);
        } else {
            $titleHelper = new OntoWiki_Model_TitleHelper();
        }
        $titleHelper->addResources(array_keys($tempResult));

        $translate = $this->_owApp->translate;

        // create different source description strings
        if (count($classes) > 0) {
            $sourceString = $translate->_('Local Search') . ' (' . $translate->_('recommended') . ')';
        } else {
            $sourceString = $translate->_('Local Search');
        }

        $result = array();
        foreach ($tempResult as $uri => $w) {
            $title = $titleHelper->getTitle($uri);

            if (null !== $title) {
                $result[$uri] = str_replace('|', '&Iota;', $title) . '|' . $uri . '|' . $sourceString;
            } else {
                $result[$uri] = OntoWiki_Utils::compactUri($uri) . $uri . '|' . $sourceString;
            }
        }

        return $result;
    }

    /**
     * Searches for properties in the local database.
     *
     * @param array  $termsArray
     * @param string $modelUri
     * @param int    $limit
     *
     * @return array
     */
    private function _searchLocalPropertiesOnly(array $termsArray, $modelUri, $limit)
    {
        require_once 'Erfurt/Sparql/SimpleQuery.php';
        $query = new Erfurt_Sparql_SimpleQuery();
        $query->setSelectClause('SELECT DISTINCT ?uri ?o');

        if (null !== $modelUri) {
            $query->addFrom($modelUri);
        }

        $where = '{ { ?uri ?p ?o . ?uri <' . EF_RDF_TYPE . '> ?o2 .
            FILTER (
                sameTerm(?o2, <http://www.w3.org/1999/02/22-rdf-syntax-ns#Property>) ||
                sameTerm(?o2, <http://www.w3.org/2002/07/owl#DatatypeProperty>) ||
                sameTerm(?o2, <http://www.w3.org/2002/07/owl#ObjectProperty>)
            )
            FILTER ((';

        $uriRegexFilter = array();
        foreach ($termsArray as $t) {
            $uriRegexFilter[] = 'regex(str(?uri), "' . $t . '", "i")';
        }
        $where .= implode(' && ', $uriRegexFilter) . ') || (isLiteral(?o) && ';

        $oRegexFilter = array();
        foreach ($termsArray as $t) {
            $oRegexFilter[] = 'regex(?o, "' . $t . '", "i")';
        }
        $where .= implode(' && ', $oRegexFilter) . ')) } UNION {';

        $where
            .= '?s ?uri ?o .
                  FILTER (';
        $where .= implode(' && ', $uriRegexFilter) . ') } }';

        $query->setWherePart($where);
        $query->setOrderClause('?uri');
        $query->setLimit($limit);

        $store       = Erfurt_App::getInstance()->getStore();
        $queryResult = $store->sparqlQuery($query, array('result_format' => 'extended'));

        $tempResult = array();
        foreach ($queryResult['results']['bindings'] as $row) {
            if ($row['o']['type'] === 'literal') {
                $weight = $this->_getWeight($termsArray, $row['uri']['value'], $row['o']['value']);
            } else {
                $weight = $this->_getWeight($termsArray, $row['uri']['value']);
            }

            if (isset($tempResult[$row['uri']['value']])) {
                if ($weight > $tempResult[$row['uri']['value']]) {
                    $tempResult[$row['uri']['value']] = $weight;
                }
            } else {
                $tempResult[$row['uri']['value']] = $weight;
            }
        }

        arsort($tempResult);

        require_once 'OntoWiki/Model/TitleHelper.php';
        require_once 'OntoWiki/Utils.php';
        if (null !== $modelUri) {
            $model       = $store->getModel($modelUri, false);
            $titleHelper = new OntoWiki_Model_TitleHelper($model);
        } else {
            $titleHelper = new OntoWiki_Model_TitleHelper();
        }
        $titleHelper->addResources(array_keys($tempResult));

        $translate = $this->_owApp->translate;
        $result    = array();
        foreach ($tempResult as $uri => $w) {
            $title = $titleHelper->getTitle($uri);

            if (null !== $title) {
                $result[$uri] = str_replace('|', '&Iota;', $title) . '|' . $uri . '|' . $translate->_('Local Search');
            } else {
                $result[$uri] = OntoWiki_Utils::compactUri($uri) . $uri . '|' .
                    $translate->_('Local Search');
            }
        }

        return $result;
    }


    // ------------------------------------------------------------------------
    // --- Statement import related methods -----------------------------------
    // ------------------------------------------------------------------------

    /**
     * Imports data available for a given URI and wrapper name.
     *
     * uri - Mandatory parameter that contains the URI to test.
     *
     * wrapper - Optional parameter, which contains the name of the wrapper
     *           to use. If not given linkeddata is used as default.
     */
    public function importAction()
    {
        // Disable rendering
        $this->_helper->viewRenderer->setNoRender();
        $this->_helper->layout()->disableLayout();

        // We require GET requests here.
        if (!$this->_request->isGet()) {
            $this->_response->setException(new OntoWiki_Http_Exception(400));

            return;
        }

        // uri param is required.
        if (!isset($this->_request->uri)) {
            $this->_response->setException(new OntoWiki_Http_Exception(400));

            return;
        }
        $uri = urldecode($this->_request->uri);

        $wrapperName = 'linkeddata';
        if (isset($this->_request->wrapper)) {
            $wrapperName = $this->_request->wrapper;
        }

        if (null === $this->_graphUri) {
            throw new OntoWiki_Exception("model must be selected or specified with the m parameter");
        }

        $presets = array();
        if (isset($this->_privateConfig->fetch->preset)) {
            $presets = $this->_privateConfig->fetch->preset->toArray();
        }

        $exceptedProperties = array();
        if (isset($this->_privateConfig->fetch->default->exception)) {
            $exceptedProperties = $this->_privateConfig->fetch->default->exception->toArray();
        }

        try {
            $res = self::import(
                $this->_graphUri,
                $uri,
                $this->_getProxyUri($uri),
                isset($this->_privateConfig->fetch->allData)
                && ((boolean)$this->_privateConfig->fetch->allData === true),
                $presets,
                $exceptedProperties,
                $wrapperName,
                $this->_privateConfig->fetch->default->mode
            );
        } catch (Exception $e) {
            if (defined('_EFDEBUG')) {
                $errorMessage = 'An error occured: ' . $e->getMessage() . " – " . $e->getTraceAsString();
                return $this->_sendResponse(false, $errorMessage, OntoWiki_Message::ERROR);
            } else {
                $res = null;
            }
        }

        if ($res == self::IMPORT_OK) {
            return $this->_sendResponse(
                true,
                'Data was found for the given URI. Statements were added.',
                OntoWiki_Message::INFO
            );
        } else {
            if ($res == self::IMPORT_WRAPPER_ERR) {
                return $this->_sendResponse(
                    false, 'The requested wrapper failed with an error.', OntoWiki_Message::ERROR
                );
            } else {
                if ($res == self::IMPORT_NO_DATA) {
                    return $this->_sendResponse(false, 'No statements were found.', OntoWiki_Message::ERROR);
                } else {
                    if ($res == self::IMPORT_NO_NEW_DATA) {
                        return $this->_sendResponse(false, 'No new statements were found.', OntoWiki_Message::ERROR);
                    } else {
                        if ($res == self::IMPORT_WRAPPER_INSTANCIATION_ERR) {
                            $this->_response->setException(new OntoWiki_Http_Exception(400));

                            return $this->_sendResponse(
                                false, 'could not get wrapper instance.', OntoWiki_Message::ERROR
                            );
                        } else {
                            if ($res == self::IMPORT_NOT_EDITABLE) {
                                $this->_response->setException(new OntoWiki_Http_Exception(403));

                                return $this->_sendResponse(
                                    false, 'you cannot write to this model.', OntoWiki_Message::ERROR
                                );
                            } else {
                                if ($res == self::IMPORT_WRAPPER_NOT_AVAILABLE) {
                                    return $this->_sendResponse(
                                        false, 'The requested wrapper is not available.', OntoWiki_Message::ERROR
                                    );
                                } else {
                                    if ($res == self::IMPORT_WRAPPER_RESULT_NOT_AVAILABLE) {
                                        return $this->_sendResponse(
                                            false, 'The requested wrapper returned no result.', OntoWiki_Message::ERROR
                                        );
                                    } else {
                                        return $this->_sendResponse(
                                            false, 'unexpected return value.', OntoWiki_Message::ERROR
                                        );
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    public static function filterStatements(
        $statements, $uri, $all = true, $presets = array(),
        $exceptedProperties = array(), $fetchMode = 'none'
    ) {
        // TODO handle configuration for import...
        if ($all) {
            // Keep all data...
            return $statements;
        } else {
            // Only use those parts of the data, that have the resource URI as subject.
            if (isset($statements[$uri])) {
                $statements = array(
                    $uri => $statements[$uri]
                );
            } else {
                $statements = array();
            }

            // We also need to remove all blank node objects
            $newResult = array();
            foreach ($statements as $s => $pArray) {
                foreach ($pArray as $p => $oArray) {
                    foreach ($oArray as $oSpec) {
                        if ($oSpec['type'] !== 'bnode') {
                            if (!isset($newResult[$s])) {
                                $newResult[$s] = array();
                            }
                            if (!isset($newResult[$s][$p])) {
                                $newResult[$s][$p] = array();
                            }
                            $newResult[$s][$p][] = $oSpec;
                        }
                    }
                }
            }
            $statements = $newResult;
        }

        $presetMatch = false;
        foreach ($presets as $i => $preset) {
            if (self::_matchUriStatic($preset['match'], $uri)) {
                $presetMatch = $i;
                break;
            }
        }

        $data   = $statements;
        $result = null;
        if ($presetMatch !== false) {
            // Use the preset
            if (isset($presets[$presetMatch]['mode']) && $presets[$presetMatch]['mode'] === 'none') {
                // Start with an empty result.
                $result = array();
                if (isset($presets[$presetMatch]['exception'])) {
                    foreach ($presets[$presetMatch]['exception'] as $exception) {
                        if (isset($data[$uri][$exception])) {
                            if (!isset($result[$uri])) {
                                $result[$uri] = array();
                            }
                            if (!isset($result[$uri][$exception])) {
                                $result[$uri][$exception] = array();
                            }

                            foreach ($data[$uri][$exception] as $o) {
                                if ($o['type'] === 'literal') {
                                    if (isset($presets[$presetMatch]['lang'])) {
                                        foreach ($presets[$presetMatch]['lang'] as $lang) {
                                            if (isset($o['lang']) && $o['lang'] === $lang) {
                                                $result[$uri][$exception][] = $o;
                                            }
                                        }
                                    } else {
                                        $result[$uri][$exception][] = $o;
                                    }
                                } else {
                                    $result[$uri][$exception][] = $o;
                                }
                            }

                            if (isset($presets[$presetMatch]['lang'])) {
                                if (count($result[$uri][$exception]) === 0) {
                                    foreach ($data[$uri][$exception] as $o) {
                                        if (!isset($o['lang'])) {
                                            $result[$uri][$exception][] = $o;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } else {
                // Use the default rule.
                // Start with all data.
                $result = $data;
                if (isset($presets[$presetMatch]['exception'])) {
                    foreach ($presets[$presetMatch]['exception'] as $exception) {
                        if (isset($data[$uri][$exception])) {
                            if (isset($result[$uri][$exception])) {
                                unset($result[$uri][$exception]);
                            }
                        }
                    }
                }
            }
        } else {
            if ($fetchMode === 'none') {
                // Start with an empty result.
                $result = array();
                foreach ($exceptedProperties as $exception) {
                    if (isset($data[$uri][$exception])) {
                        if (!isset($result[$uri])) {
                            $result[$uri] = array();
                        }
                        $result[$uri][$exception] = $data[$uri][$exception];
                    }
                }
            } else {
                // Start with all data.
                $result = $data;
                foreach ($exceptedProperties as $exception) {
                    if (isset($data[$uri][$exception])) {
                        if (isset($result[$uri][$exception])) {
                            unset($result[$uri][$exception]);
                        }
                    }
                }
            }
        }
        $statements = $result;

        return $statements;
    }

    /**
     *
     * @param string  $graphUri
     * @param string  $uri
     * @param type    $locator
     * @param boolean $all
     * @param array   $presets
     * @param array   $exceptedProperties
     * @param string  $wrapperName
     * @param string  $fetchMode
     * @param string  $action
     * @param boolean $versioning
     *
     * @return int status code
     */
    //TODO refactor these 11 parameters (use a config object or break it down with adapters etc)
    public static function import(
        $graphUri, $uri, $locator, $all = true, $presets = array(),
        $exceptedProperties = array(), $wrapperName = 'linkeddata', $fetchMode = 'none',
        $action = 'add', $versioning = true, $filterCallback = null
    ) {
        // Check whether user is allowed to write the model.
        $erfurt     = Erfurt_App::getInstance();
        $store      = $erfurt->getStore();
        $storeGraph = $store->getModel($graphUri);
        if (!$storeGraph || !$storeGraph->isEditable()) {
            return self::IMPORT_NOT_EDITABLE;
        }

        $r = new Erfurt_Rdf_Resource($uri);
        $r->setLocator($locator);

        // Try to instanciate the requested wrapper
        $wrapper = null;
        try {
            $wrapper = Erfurt_Wrapper_Registry::getInstance()->getWrapperInstance($wrapperName);
        } catch (Erfurt_Wrapper_Exception $e) {
            return self::IMPORT_WRAPPER_INSTANCIATION_ERR;
        }
        if (null == $wrapper) {
            return self::IMPORT_WRAPPER_NOT_AVAILABLE;
        }

        $wrapperResult = null;
        try {
            $wrapperResult = $wrapper->run($r, $graphUri, $all);
        } catch (Erfurt_Wrapper_Exception $e) {
            return self::IMPORT_WRAPPER_ERR;
        }

        if ($wrapperResult === false) {
            return self::IMPORT_WRAPPER_RESULT_NOT_AVAILABLE;
        } else {
            if (is_array($wrapperResult)) {
                if (isset($wrapperResult['status_codes'])) {
                    if (in_array(Erfurt_Wrapper::RESULT_HAS_ADD, $wrapperResult['status_codes'])) {
                        $newStatements = $wrapperResult['add'];

                        //default filter
                        $newStatements = self::filterStatements(
                            $newStatements, $uri, $all, $presets, $exceptedProperties, $fetchMode
                        );

                        //custom filter
                        if ($filterCallback != null && is_array($filterCallback)) {
                            try {
                                $newStatements = call_user_func($filterCallback, $newStatements);
                            } catch (Exception $e) {
                                return self::IMPORT_CUSTOMFILTER_EXCEPTION;
                            }
                        }

                        $stmtBeforeCount = $store->countWhereMatches(
                            $graphUri,
                            '{ ?s ?p ?o }',
                            '*'
                        );

                        if ($versioning) {
                            // Prepare versioning...
                            $versioning = $erfurt->getVersioning();
                            $actionSpec = array(
                                'type'        => self::VERSIONING_IMPORT_ACTION_TYPE,
                                'modeluri'    => $graphUri,
                                'resourceuri' => $uri
                            );

                            // Start action
                            $versioning->startAction($actionSpec);
                        }

                        if ($action == 'add') {
                            try {
                                $store->addMultipleStatements($graphUri, $newStatements);
                            } catch (Exception $e) {
                                $versioning->abortAction();

                                if (defined('_EFDEBUG')) {
                                    throw $e;
                                }

                                return self::IMPORT_GENERAL_EXCEPTION;
                            }
                        } else {
                            if ($action == 'update') {
                                $queryoptions  = array(
                                    'use_ac'                 => false,
                                    'result_format'          => Erfurt_Store::RESULTFORMAT_EXTENDED,
                                    'use_additional_imports' => false
                                );
                                $oldStatements = $store->sparqlQuery(
                                    'SELECT * FROM <' . $graphUri . '> WHERE { ?s ?p ?o }',
                                    $queryoptions
                                );
                                //transform resultset to rdf/php statements
                                $modelOld = new Erfurt_Rdf_MemoryModel();
                                $modelOld->addStatementsFromSPOQuery($oldStatements);
                                $modelNew = new Erfurt_Rdf_MemoryModel($newStatements);
                                $storeGraph->updateWithMutualDifference(
                                    $modelOld->getStatements(),
                                    $modelNew->getStatements()
                                );
                            }
                        }

                        if ($versioning) {
                            $versioning->endAction();
                        }

                        $stmtAfterCount = $store->countWhereMatches(
                            $graphUri,
                            '{ ?s ?p ?o }',
                            '*'
                        );

                        $stmtAddCount = $stmtAfterCount - $stmtBeforeCount;

                        if ($stmtAddCount > 0) {
                            // TODO test ns
                            // If we added some statements, we check for additional namespaces and add them.
                            if (in_array(Erfurt_Wrapper::RESULT_HAS_NS, $wrapperResult['status_codes'])) {
                                $namespaces = $wrapperResult['ns'];

                                $erfurtNamespaces = $erfurt->getNamespaces();

                                foreach ($namespaces as $ns => $prefix) {
                                    try {
                                        $erfurtNamespaces->addNamespacePrefix(
                                            $graphUri,
                                            $prefix,
                                            $ns,
                                            false
                                        );
                                    } catch (Exception $e) {
                                        // Ignore...
                                    }
                                }
                            }

                            return self::IMPORT_OK;

                        } else {
                            if (count($newStatements) > 0) {
                                return self::IMPORT_NO_NEW_DATA;
                            } else {
                                return self::IMPORT_NO_DATA;
                            }
                        }
                    } else {
                        return self::IMPORT_NO_DATA;
                    }
                } else {
                    return self::IMPORT_WRAPPER_ERR;
                }
            } else {
                return self::IMPORT_WRAPPER_ERR;
            }
        }
    }

    private function _sendResponse($returnValue, $message = null, $messageType = OntoWiki_Message::SUCCESS)
    {
        if (null !== $message) {
            $translate = $this->_owApp->translate;

            $message = $translate->_($message);
            $this->_owApp->appendMessage(
                new OntoWiki_Message($message, $messageType)
            );
        }

        $returnValue = array(
            'code'    => $returnValue,
            'message' => $message
        );

        $this->_response->setHeader('Content-Type', 'application/json', true);
        $this->_response->setBody(json_encode($returnValue));
    }

    // ------------------------------------------------------------------------
    // --- Statement sync related methods -------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Filters a result regarding the configured sparql query.
     *
     * @param array  $result
     * @param string $uri
     * @param string $wrapperName
     * @param string $modelUri
     *
     * @return array
     */
    private function _filterResult($result, $uri, $wrapperName, $modelUri)
    {
        $store      = Erfurt_App::getInstance()->getStore();
        $syncConfig = $this->_getSyncConfig($uri, $wrapperName, $modelUri);

        if (!is_array($syncConfig) || count($syncConfig) === 0) {
            return array();
        }
        $syncConfig = $syncConfig[0];

        // TODO We need support for in-memory models... this is a workaround
        $tempModelUri = $this->_syncModelHelperBase . md5($uri . $wrapperName . $modelUri . time());
        $tempModel    = $store->getNewModel($tempModelUri, '', 'rdf', false);
        $store->addMultipleStatements($tempModelUri, $result);
        $simpleQuery = Erfurt_Sparql_SimpleQuery::initWithString($syncConfig['syncQuery']);
        $simpleQuery->addFrom($tempModelUri);
        $sparqlResult = $store->sparqlQuery($simpleQuery, array('result_format' => 'extended', 'use_ac' => false));
        $store->deleteModel($tempModelUri);

        $retVal = array();
        foreach ($sparqlResult['results']['bindings'] as $row) {
            if (!isset($retVal[$row['s']['value']])) {
                $retVal[$row['s']['value']] = array();
            }
            if (!isset($retVal[$row['s']['value']][$row['p']['value']])) {
                $retVal[$row['s']['value']][$row['p']['value']] = array();
            }

            $oArray = array(
                'value' => $row['o']['value']
            );

            if ($row['o']['type'] === 'typed-literal') {
                $oArray['type']     = 'literal';
                $oArray['dataytpe'] = $row['o']['datatype'];
            } else {
                $oArray['type'] = $row['o']['type'];
            }

            if (isset($row['o']['xml:lang'])) {
                $oArray['lang'] = $row['o']['xml:lang'];
            }

            $retVal[$row['s']['value']][$row['p']['value']][] = $oArray;
        }

        return $retVal;
    }

    /**
     * Fetches the data from the wrapper.
     *
     * @param string $uri
     * @param string $wrapperName
     * @param string $modelUri
     *
     * @return array
     */
    private function _getData($uri, $wrapperName, $modelUri)
    {
        try {
            $wrapper = $this->_wrapperRegistry->getWrapperInstance($wrapperName);

            $wrapperResult = $wrapper->run($uri, $modelUri);
            if (is_array($wrapperResult) && isset($wrapperResult['add'])) {
                $wrapperResult = $wrapperResult;
            } else {
                $wrapperResult = array();
            }
        } catch (Exception $e) {
            $wrapperResult = array();
        }

        return $wrapperResult;
    }


    private function _getProxyUri($uri)
    {
        // If at least one rewrite rule is defined, we iterate through them.
        if (isset($this->_privateConfig->rewrite)) {
            $rulesArray = $this->_privateConfig->rewrite->toArray();
            foreach ($rulesArray as $ruleId => $ruleSpec) {
                $proxyUri = preg_replace($ruleSpec['pattern'], $ruleSpec['replacement'], $uri);
                if ($proxyUri !== $uri) {
                    return $proxyUri;
                }
            }
        }

        return null;
    }

    private function _matchUri($pattern, $uri)
    {
        if ((substr($pattern, 0, 7) !== 'http://')) {
            $pattern = 'http://' . $pattern;
        }

        if ((substr($uri, 0, strlen($pattern)) === $pattern)) {
            return true;
        } else {
            return false;
        }
    }

    private static function _matchUriStatic($pattern, $uri)
    {
        if ((substr($pattern, 0, 7) !== 'http://')) {
            $pattern = 'http://' . $pattern;
        }

        if ((substr($uri, 0, strlen($pattern)) === $pattern)) {
            return true;
        } else {
            return false;
        }
    }
}
