<?php
/**
 * Please see weepie-framework.php for more details.
 */

namespace WpieFw\Settings;

use WpieFw\Wpie\WpieGlobals;
use WpieFw\Templates\WpieTemplate;
use WpieFw\Templates\Files\WpieTemplatesFileFinder;
use WpieFw\Helpers\WpieAjaxHelper;
use WpieFw\Helpers\WpieMiscHelper;
use WpieFw\PostTypes\WpiePostType;
use WpieFw\Settings\Iterator\WpieSettingsCollection;
use WpieFw\Settings\Export\WpieSettingsExporter;
use WpieFw\Exceptions\WpieExceptionLogger;
use WpieFw\Exceptions\WpieExceptionInterface;
use WpieFw\Exceptions\WpieUnexpectedValueException;
use WpieFw\Notices\WpieNotices;

if( ! defined( 'ABSPATH' ) ) exit;

/**
 * WpieSettingsPage Class
 *
 * Class for handling Plugin Settings page
 *
 * @author $Author: Vincent Weber <weepie-plugins@outlook.com> $
 * @since
 */
final class WpieSettingsPage
{
	/**
	 * The setting page hook_suffix values
	 *
	 * @since 1.2
	 *
	 * @var array
	 */
	public $hooknames = [];

	/**
	 * Flag if current request is for one of the settings pages
	 *
	 * @since 1.2
	 *
	 * @var bool
	 */
	public $onSettingsPage = false;

	/**
	 * URI to the settings page
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	public $uri;

	/**
	 * URI to the default settings page
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	public $uriTabDefault = null;

	/**
	 * The current tab name
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	public $currentTabIdx = null;

	/**
	 * The current tab page slug
	 *
	 * query var 'page' value
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	public $currentTabSlug = null;

	/**
	 * Absolute path to the Templates folder
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	private $templatePath;

	/**
	 * Container for the tabs
	 *
	 * @since 1.2
	 *
	 * @var array
	 */
	private $tabs = [];

	/**
	 * The tab flagged as default
	 *
	 * @since 1.2
	 *
	 * @var array
	 */
	private $defaultTab = [];

	/**
	 * The tab flagged as default index
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	private $defaultTabIdx = null;

	/**
	 * Flag if more than 1 tab is available
	 *
	 * @var bool
	 */
	private $hasTabs = false;

	/**
	 * The page title
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	private $pageTitle;

	/**
	 * The menu title
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	private $menuTitle;

	/**
	 * Prefix that all settings pages have in the slug
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	private $slug = 'wpie-%s-settings';

	/**
	 * Page slug of the parent page
	 *
	 * In case of multiple tabs
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	private $parentSlug = null;

	/**
	 * Container for all WpiePluginSettings instances for the current namespace
	 *
	 * @since 1.2
	 *
	 * @var WpieSettingsCollection
	 */
	private $settings = [];

	/**
	 * Allowed AJAX actions
	 *
	 * @since 2.0.5
	 *
	 * @var array
	 */
	private $allowedAjaxActions = [
		'wpie-add-post',
		'wpie-del-post',
		'wpie-update-post',
		'wpie-settings-export',
		'wpie-settings-import',
		'wpie-dismiss-upgrade-notice'
	];

	/**
	 * Post AJAX actions
	 *
	 * @since 2.0.5
	 *
	 * @var array
	 */
	private $postAjaxActions = [
		'wpie-add-post',
		'wpie-del-post',
		'wpie-update-post'
	];

	/**
	 * WpieGlobals instance
	 *
	 * @since 1.4.0
	 *
	 * @var WpieGlobals
	 */
	private $globals = false;

	/**
	 * File name for the settings JavaScript
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	const FILE_NAME_SETTINGS_JS = 'wpie-settings';

	/**
	 * File name for the settings lists JavaScript
	 *
	 * @since 1.4.0
	 *
	 * @var string
	 */
	const FILE_NAME_SETTINGS_LISTS_JS = 'wpie-settings-lists';

	/**
	 * File name for the settings page css
	 *
	 * @since 1.4.7
	 *
	 * @var string
	 */
	const FILE_NAME_SETTINGS_CSS = 'admin-settings-page';

	/**
	 * Template file name for the settings page wrapper
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	const TEMPL_FILE_NAME_SETTINGS_PAGE = 'wpie-tpl-settings-page.php';

	/**
	 * Template file name for the settings page content table
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	const TEMPL_FILE_NAME_SETTINGS_PAGE_TABLE = 'wpie-tpl-settings-page-table.php';

	/**
	 * Template file name for one Custom Post Type list item
	 *
	 * @since 1.2.1
	 *
	 * @var string
	 */
	const TEMPL_FILE_NAME_LIST_ITEMS = 'wpie-tpl-list-items.php';

	/**
	 * Template file name for one list item
	 *
	 * @since 1.2.1
	 *
	 * @var string
	 */
	const TEMPL_FILE_NAME_LIST_ITEM = 'wpie-tpl-list-item.php';

	/**
	 * constructor
	 *
	 * @access public
	 *
	 * @param string $pageTitle
	 * @param string $menuTitle
	 * @param WpieGlobals $globals
	 *
	 * @since 1.2
	 */
	public function __construct( string $pageTitle, string $menuTitle, WpieGlobals $globals )
	{
		$this->globals = $globals;
		$this->pageTitle = $pageTitle;
		$this->menuTitle = $menuTitle;
		$this->templatePath = $this->globals->get( 'wfTemplPath' );
		$this->slug = sprintf( $this->slug, $this->globals->nameSpace );

		// if the current page request has a 'page' query var added, set some class members
		if ( isset( $_GET['page'] ) && false !== strpos(  $_GET['page'], $this->slug ) ) {
			// allow tab names as words or seperated words like general, general-special
			if( preg_match( '/^'.$this->slug.'-([a-z_]+)$/', $_GET['page'], $m ) ) {
				$this->currentTabIdx = $m[1];
				$this->currentTabSlug = $_GET['page'];
				$this->onSettingsPage = true;
			}

			// init WpieNotices logic by creating an instance
			// the instance() method will hook into the all_admin_notices action
			if( $this->onSettingsPage ) {
			    WpieNotices::instance();
			}
		}
	}

	/**
	 * Callback for the {$nameSpace}_scripts_admin hook
	 *
	 * Enqueue jQuery scripts for the settings pages
	 *
	 * @access public
	 *
	 * @param string $hook_suffix
	 * @param \WP_Scripts $wp_scripts
	 * @param bool $isScriptDebug
	 *
	 * @uses wp_enqueue_script()
	 * @uses wp_localize_script()
	 *
	 * @since 1.2
	 */
	public function setScripts( $hook_suffix, $wp_scripts, $isScriptDebug )
	{
		// make sure scripts are only enqueued on the settings pages
		if( in_array( $hook_suffix, $this->hooknames ) )
		{
			/**
			 * This hook let other Modules / Plugins enqueue scripts on the settings pages before {$nameSpace}-settings
			 *
			 * @param string $hook_suffix
			 * @param WP_Scripts $wp_scripts
			 * @param bool $isScriptDebug
			 */
			do_action( $this->globals->nameSpace . '_settings_scripts_before', $hook_suffix, $wp_scripts, $isScriptDebug );

			$jsUri = $this->globals->get( 'jsUri' );
			$wfJsUri = $this->globals->get( 'wfJsUri' );
			$pluginPath = $this->globals->get( 'pluginPath' );
			$isScriptDebug = WpieMiscHelper::isScriptDebug();
			$ext = ( $isScriptDebug ) ? '.js' : '.min.js';

			$filename = self::FILE_NAME_SETTINGS_JS . $ext;
			$filenameList = self::FILE_NAME_SETTINGS_LISTS_JS . $ext;

			wp_enqueue_script( 'wpie-settings', $wfJsUri . '/'. $filename, [ 'jquery' ], $this->globals->get( 'versionWf' ), true );
			wp_enqueue_script( 'wpie-settings-lists', $wfJsUri . '/'. $filenameList, [ 'wpie-settings' ], $this->globals->get( 'versionWf' ), true );

			// enqueue plugin settings JS file if exists
			if( file_exists( $pluginPath . '/assets/js/settings' . $ext ) ) {
				wp_enqueue_script( $this->globals->nameSpace.'-settings', $jsUri . '/settings' . $ext, [ 'wpie-settings' ], $this->globals->get('version'), true );
			}

			/**
			 * This hook let other Modules / Plugins enqueue scripts on the settings pages after {$nameSpace}-settings
			 *
			 * @param string $hook_suffix
			 * @param WP_Scripts $wp_scripts
			 * @param bool $isModeDev
			 */
			do_action( $this->globals->nameSpace . '_settings_scripts_after', $hook_suffix, $wp_scripts, $isScriptDebug );

			$l10nData = [];
			$l10nData['del_confirm_post'] = __( 'Are you sure you want to DELETE the selected items?', 'weepie' );
			$l10nData['del_confirm_cat'] = __( 'Are you sure you want to DELETE the selected item?', 'weepie' );
			$l10nData['post_empty'] = __( 'Cannot save item, one or more fields are empty.', 'weepie' );
			$l10nData['unknown_error'] = __( 'An Unknown error occurred. Please check your latest modification(s).', 'weepie' );
			$l10nData['error_occured'] = __( 'An error occured. Please check the error log file or browser console for more information.', 'weepie' );
			$l10nData['updating'] = __( 'updating...', 'weepie' );

			// add global JavaScript vars for $l10nData array
			wp_localize_script( 'wpie-settings', $this->globals->nameSpace . 'Datal10n', apply_filters( $this->globals->nameSpace . '_settings_scripts_l10n_data',  $l10nData ) );
		}
	}

	/**
	 * Callback for the {$nameSpace}_styles_admin hook
	 *
	 * Enqueue styles for the settings pages
	 *
	 * @access public
	 *
	 * @param string $hook_suffix
	 * @param \WP_Styles $wp_styles
	 * @param bool $isScriptDebug
	 *
	 * @uses wp_enqueue_style()
	 * @uses wp_localize_script()
	 *
	 * @since 0.1
	 */
	public function setStyles( $hook_suffix, $wp_styles, $isScriptDebug )
	{
		// make sure styles are only enqueued on the settings pages
		if( in_array( $hook_suffix, $this->hooknames ) ) {
		    $ext = ( $isScriptDebug ) ? '.css' : '.min.css';

			wp_enqueue_style( 'wpie-settings', $this->globals->get( 'wfCssUri' ) .'/' . self::FILE_NAME_SETTINGS_CSS . $ext, [], $this->globals->get( 'versionWf' ) );

			// load plugin admin settings page if exist
			if( file_exists( $this->globals->get( 'assetsPath' ) . '/css/' . self::FILE_NAME_SETTINGS_CSS . $ext ) ) {
				wp_enqueue_style( $this->globals->nameSpace.'-settings', $this->globals->get( 'cssUri' ) . '/' . self::FILE_NAME_SETTINGS_CSS . $ext, [ 'wpie-settings' ], $this->globals->get('version') );
			}

			/**
			 * This hook let other Modules / Plugins enqueue styles on the settings pages after {$nameSpace}-settings
			 *
			 * @param WP_Styles $wp_styles
			 * @param bool $isScriptDebug
			 */
			do_action( $this->globals->nameSpace . '_settings_styles', $wp_styles, $isScriptDebug );
		}
	}

	/**
	 * @param string $hook_suffix
	 */
	public function printStyles( $hook_suffix )
	{
		// make sure styles are only enqueued on the settings pages
		if( in_array( $hook_suffix, $this->hooknames ) ) {
			$logoPath = $this->globals->get( 'pluginPath' ) . '/assets/img/logo.svg';

			if( !file_exists( $logoPath ) ) {
				$logoUri = $this->globals->get( 'wfImgUri' ) . '/logo-settings.svg';
			} else {
				$logoUri = $this->globals->get( 'imgUri' ) . '/logo.svg';
			}
		?>
		<style type="text/css">
		#wpie-plugin-header .logo { background-image:url("<?php echo $logoUri ?>"); }
		<?php do_action( $this->globals->nameSpace . '_print_settings_styles' ); ?>
		</style>
		<?php
		}
	}

	/**
	 * Get a tab from the tabs array
	 *
	 * @access	public
	 *
	 * @param string 	$idx the tab index key
	 *
	 * @uses 	self::getTabs()
	 *
	 * @since 	1.2
	 *
	 * @return 	array the tab or empty array if tab is not found
	 */
	public function getTab( $idx = null )
	{
		if( null == $idx ) {
			return [];
		}

		$tabs = $this->getTabs();

		if( isset( $tabs[$idx] ) ) {
			return $tabs[$idx];
		} else {
			return [];
		}
	}

	/**
	 * Get the first tab from the tabs array
	 *
	 * @access public
	 *
	 * @uses self::getTabs()
	 *
	 * @since 1.2
	 *
	 * @return array
	 */
	public function getFirstTab()
	{
		$tabs = $this->getTabs();

		return array_shift( $tabs );
	}

	/**
	 * Get the tabs
	 *
	 * @access public
	 *
	 * @since 1.2
	 *
	 * @return array
	 */
	public function getTabs()
	{
		return $this->tabs;
	}

	/**
	 * Get the tab that is flagged as default
	 *
	 * @access public
	 *
	 * @uses self::getTabs()
	 * @uses self::getFirstTab() if default tab is not found with self::getTabs()
	 *
	 * @since 1.2
	 *
	 * @return array
	 */
	public function getDefaultTab()
	{
		if( !empty( $this->defaultTab ) ) {
			return $this->defaultTab;
		} else {
			$tabs = $this->getTabs();
			$default = [];

			foreach ( $tabs as $k => $tab ) {
				if( $tab['default'] ) {
					$default = $tab;
					break;
				}
			}

			if( empty( $default ) ) {
				$default = $this->getFirstTab();
			}

			return $default;
		}
	}

	/**
	 * Get the index for the default tab
	 *
	 * @access public
	 *
	 * @uses self::getDefaultTab() OR
	 * @uses self::getFirstTab() if tab is not found with self::getDefaultTab()
	 *
	 * @since 1.2
	 *
	 * @return string
	 */
	public function getDefaultTabIdx()
	{
		$tab = $this->getDefaultTab();

		if( !isset( $tab['idx'] ) ) {
			$tab = $this->getFirstTab();
		}

		return $tab['idx'];
	}

	/**
	 * Get a tab parameter
	 *
	 * @access public
	 *
	 * @uses self::getTab()
	 *
	 * @since 1.2
	 *
	 * @return mixed the tab param value or bool false on failure
	 */
	public function getTabParam( $idx = null, $needle = null)
	{
		if( null == $idx && null == $needle ) {
			return false;
		}

		$tab = $this->getTab( $idx );
		if( !empty( $tab ) && isset( $tab[$needle] ) ) {
			return $tab[$needle];
		} else {
			return false;
		}
	}

	/**
	 * Check if has tabs
	 *
	 * @access public
	 *
	 * @since 1.4.0
	 *
	 * @return boolean
	 */
	public function hasTabs()
	{
		$tabs = $this->getTabs();
		if( !empty( $tabs ) ) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * @param bool $activating
	 */
	public function activating( $activating )
	{
		if( $activating ) {
			$this->setTabs();
		}
	}

	/**
	 * Callback for the {nameSpace}_ajax_json_return hook
	 *
	 * Filter the AJAX return array based on given $action, actions are:
	 *
	 *  - wpie-settings-export : Export settings
	 *  - add-prop			   : Add a new Property
	 *  - del-prop			   : Delete a Property
	 *  - update-prop		   : Update a Property or Property Meta
	 *
	 * @access	public
	 *
	 * @param   mixed	$return the value that is returned to the client
	 * @param   string 	$action the 'wpie-action' that is defined at the client-side (JavaScript)
	 * @param   array 	$data (optional) extra data to pass from client-side to here
	 *
	 * @since 	1.2.1
	 *
	 * @return mixed
	 */
	public function process( $return, $action, $data )
	{
		// if action does not match one of the following, return directly
		if( !in_array( $action, $this->allowedAjaxActions ) ) {
			return $return;
		}

		if( in_array( $action, $this->postAjaxActions ) ) {
			// To continue, a Post ID is needed
			if( ( 'wpie-del-post' === $action || 'wpie-update-post' === $action ) && !isset( $data['post_id'] ) ) {
				return $return;
			} elseif( ( 'wpie-del-post' === $action || 'wpie-update-post' === $action ) && isset( $data['post_id'] ) ) {
				$postId = $data['post_id'];
			} else {
				$postId = 0;
			}

			if( 'wpie-add-post' === $action && ( !isset( $data['post_type'] ) || '' === $data['post_type'] ) ) {
				return $return;
			} elseif( 'wpie-add-post' === $action && isset( $data['post_type'] ) ) {
				$postType = $data['post_type'];
			} else {
				$postType = null;
			}

			if( 'wpie-add-post' !== $action && false !== ( $postType = get_post_type( is_array( $postId ) ? $postId[0] : $postId ) ) ) {
				$postTypeObj = new WpiePostType( $postType );
			} elseif( 'wpie-add-post' === $action && null !== $postType ) {
				$postTypeObj = new WpiePostType( $postType );
			} else {
				$postTypeObj = null;
			}

			if( null === $postTypeObj ) {
				return $return;
			}
		}

		if( 'wpie-settings-import' === $action && ( !isset( $data['content'] ) || '' === $data['content'] ) ) {
			return $return;
		} else {
			$exporter = new WpieSettingsExporter( $this->settings, $this->globals );
		}

		if( 'wpie-dismiss-upgrade-notice' === $action && !isset( $data['ns'] ) ) {
			return $return;
		}

		$context = ( isset( $data['context'] ) ) ? $data['context'] : '';

		switch( $action ) {
			case 'wpie-dismiss-upgrade-notice':
				delete_transient( $data['ns'] . '_upgrade_msg' );
			break;
			case 'wpie-settings-export':
				$return['exported'] = $exporter->export();
				$return['msg'] = sprintf( __( 'Copy the content above or %s here.', 'weepie' ),
				                          '<a id="wpie-export-file-link" href="" download="' . sprintf( 'wpca-settings-%s.txt', date( 'Y-m-d', time() ) ) . '">download</a>' );
			break;
			case 'wpie-settings-import':
				try {
					$exporter->import( $data['content'] );
					$return['imported'] = true;
					$return['msg'] = __( 'Import succeeded. Reloading page a few seconds...', 'weepie' );
				} catch(WpieUnexpectedValueException $exc) {
					$return['imported'] = false;
					$return['msg'] = $exc->getMessage();
				}

			break;
			case 'wpie-add-post':
				if( !isset( $data['val'] ) ) {
					return $return;
				} else {
					$title = $data['val'];
				}

				$canDel = ( isset( $data['can_del'] ) ) ? $data['can_del'] : false;
				$canSave = ( isset( $data['can_save'] ) ) ? $data['can_save'] : false;
				$hasTitle = ( isset( $data['has_title'] ) ) ? $data['has_title'] : false;
				$hasMedia = ( isset( $data['has_media'] ) ) ? $data['has_media'] : false;
				$clickSelect = ( isset( $data['click_select'] ) ) ? $data['click_select'] : false;

				if( $postId = $postTypeObj->create( $title ) ) {
					$title = get_the_title( $postId );

					/**
					 * Let modules hook into the ajax add post process
					 *
					 * @param	int		$postId
					 * @param string	$context
					 * @param	array	$data
					 *
					 * @since	1.2.2beta
					 */
					do_action( 'wpie_'. $this->globals->nameSpace .'_ajax_post_action_add', $postId, $context, $data );

					/**
					 * Let modules hook into the ajax add post process
					 *
					 * @param	int		$postId
					 * @param string	$context
					 * @param	array	$data
					 * @param string	$action
					 *
					 * @since	1.2.2beta
					 */
					do_action( 'wpie_'. $this->globals->nameSpace .'_ajax_post_action', $postId, $context, $data, $action );

					// Exceptions should be catched by WpieHooks::processAjaxRequest()
					$finder = new WpieTemplatesFileFinder( $this->templatePath, '', '', self::TEMPL_FILE_NAME_LIST_ITEM, false );
					$templ = new WpieTemplate( $finder, self::TEMPL_FILE_NAME_LIST_ITEM );

					$templ->setVar( 'post_id', $postId );
					$templ->setVar( 'post_type', $postType );
					$templ->setVar( 'title', $title );
					$templ->setVar( 'can_del', $canDel );
					$templ->setVar( 'can_save', $canSave );
					$templ->setVar( 'has_title', $hasTitle );
					$templ->setVar( 'context', $context );
					$templ->setVar( 'click_select', $clickSelect );
					$templ->setVar( 'selected', 0 );

					$return['id'] = $postId;
					$return['title'] = $title;
					$return['template'] = $templ->render( false, true );

				} else {
					$return = false;
				}
				break;

			case 'wpie-del-post':
				$notdeleted = [];
				foreach( (array)$postId as $id ) {
					if( $postTypeObj->delete( $id ) ) {
						$return['deleted'][] = $id;

						/**
						 * Let modules hook into the ajax delete post process
						 *
						 * @param	int		$id
						 * @param string	$context
						 * @param	array	$data
						 *
						 * @since	1.2.2beta
						 */
						do_action( 'wpie_'. $this->globals->nameSpace .'_ajax_post_action_del', $id, $context, $data );

						/**
						 * Let modules hook into the ajax delete post process
						 *
						 * @param	int		$id
						 * @param string	$context
						 * @param	array	$data
						 * @param string	$action
						 *
						 * @since	1.2.2beta
						 */
						do_action( 'wpie_'. $this->globals->nameSpace .'_ajax_post_action', $id, $context, $data, $action );
					} else {
						$notdeleted[] = $id;
					}
				}
				if( !empty( $notdeleted ) ) {
					$return = false;
				}
				break;
			case 'wpie-update-post':
				if( !isset( $data['val'] ) ) {
					return $return;
				} else {
					$titleNew	= $data['val'];
					$hasGroupedMeta = false;
				}

				// determine if current action must handle post meta as one group (array)
				if( isset( $data['group_meta'] ) && ( 'true' === $data['group_meta'] || true === $data['group_meta'] ) ) {
					$hasGroupedMeta = true;

					if( !isset( $data['group_meta_key'] ) || '' === $data['group_meta_key'] ) {
						return false;
					}

					$groupedMetaKey = $data['group_meta_key'];
				}

				$post =  $postTypeObj->fetch( $postTypeObj->retrieve( $postId ) );
				$title = array_shift( $post )->post_title;
				$args = $postTypeObj->getArgsNew( $titleNew );

				// if the title has been modified, update the Post slug
				if( $title !== $titleNew ) {
					$args['post_name'] = sanitize_title( $titleNew );
				}

				$return['changed'] = false;
				$return['post_id'] = $postTypeObj->update( $postId, $args, true );

				if( WpieAjaxHelper::isValidData( $return['post_id'] ) ) {
					/**
					 * Let modules hook into the ajax update post process
					 *
					 * @param	int		$id
					 * @param string	$context
					 * @param	array	$data
					 *
					 * @since	1.2.2beta
					 */
					do_action( 'wpie_'. $this->globals->nameSpace .'_ajax_post_action_update', $postId, $context, $data );

					/**
					 * Let modules hook into the ajax update post process
					 *
					 * @param	int		$id
					 * @param string	$context
					 * @param	array	$data
					 * @param string	$action
					 *
					 * @since	1.2.2beta
					 */
					do_action( 'wpie_'. $this->globals->nameSpace .'_ajax_post_action', $postId, $context, $data, $action );

					/**
					 * Let modules hook and alter the the ajax update post data
					 *
					 * @param	array	$data
					 * @param	int		$postId
					 * @param string	$context
					 *
					 * @since	1.2.2beta
					 *
					 * @return	array
					 */
					$data_filtered = apply_filters( 'wpie_'. $this->globals->nameSpace .'_ajax_post_action_update_data', $data, $postId, $context );

					// be sure the filtered $data has the 'post_meta' entry
					if( is_array( $data_filtered ) && isset( $data_filtered['post_meta']) ) {
						$data = $data_filtered;
					}

					// return 0 instead of false to prevent an invalid ajax return
					$return['changed'] = ( $title !== $titleNew ) ? 1 : 0;

					if( isset( $data['post_meta'] ) ) {
						$metaAdded = false;
						$postMeta = $data['post_meta'];

						if( $hasGroupedMeta ) {
							$current = get_post_meta( $postId, $groupedMetaKey, true );
							if( is_array( $current ) && !empty( $current ) ) {
								$postMeta = array_merge( $current, $postMeta );
							}
							$metaAdded = update_post_meta( $postId, $groupedMetaKey, $postMeta );
							if( $metaAdded ) {
								foreach ( $postMeta as $k => $v ) {
									$return['meta_added'][$k] = $v;
								}
							}
						} else {
							foreach ( $postMeta as $k => $v ) {
								switch( $k ) {
									default:
										$metaAdded = update_post_meta( $postId, $k, $v );
										break;
								}

								if( false === $metaAdded ) {
									// meta not added
									$return['meta_not_added'][$k] = $v;

								} else {
									// meta succesfully added
									$return['meta_added'][$k] = $v;
								}
							}
						}
					}
				}
				break;
		}

		return $return;
	}

	/**
	 * Callback for the {nameSpace}_after_init_modules hook
	 *
	 * @todo: 	detailed description
	 *
	 * @acces 	public
	 *
	 * @since 	1.2
	 */
	public function hook()
	{
		add_action( $this->globals->nameSpace . '_scripts_admin', [ $this, 'setScripts' ], 10, 3 );
		add_action( $this->globals->nameSpace . '_styles_admin', [ $this, 'setStyles' ], 10, 3 );
		add_action( $this->globals->nameSpace . '_print_styles_admin', [ $this, 'printStyles' ] );

		add_action( $this->globals->nameSpace . '_add_admin_pages', [ $this, 'addPage' ] );

		$pluginFile = $this->globals->get( 'pluginFile' );
		add_filter( 'plugin_action_links_' .$pluginFile, [ $this, 'addPluginActionLink' ], 10 );

		add_action( $this->globals->nameSpace . '_module_before_start', [ $this, 'activating' ], 1 );

		add_filter( $this->globals->nameSpace . '_ajax_json_return', [ $this,'process' ], 10, 3 );

		add_filter( 'admin_body_class', function( $classes ) {
			if($this->onSettingsPage) {
				$classes .= ' wpie-settings-page';
			}

			return $classes;
		} );

		// add settings name (group) for each tab
		foreach ( $this->tabs as $k => $data ) {
			$this->tabs[$k]['setting'] = $this->settings[$k]->getName();
		}

		/**
		 * This hook let other Modules / Plugins add a setting group
		 *
		 * @param array all tabs that represent a setting
		 */
		$this->tabs = apply_filters( $this->globals->nameSpace . '_setting_groups', $this->tabs );

		foreach( $this->tabs as $key => $tab ) {
			if( !isset( $tab['slug'] ) ) {
				$this->addTabData( $key );
			}
		}
	}

	/**
	 * Render the admin settings page
	 *
	 * @acces public
	 *
	 * @uses WpieTemplate to render the output
	 *
	 * @since 1.2
	 */
	public function render()
	{
		$tabidx = ( $this->hasTabs ) ? $this->currentTabIdx : $this->defaultTabIdx;
		$setting = $this->tabs[$tabidx]['setting'];

		try {
			// init the parent template
			$finder = new WpieTemplatesFileFinder( $this->templatePath, '', '', self::TEMPL_FILE_NAME_SETTINGS_PAGE, false );
			$template = new WpieTemplate( $finder, 'settings' );

			// set vars for the template
			$template->setVar( 'current_tab', $tabidx );
			$template->setVar( 'current_tab_nice', WpieMiscHelper::convertToHyphens( $tabidx ) );
			$template->setVar( 'setting', $setting );
			$template->setVar( 'has_tabs', $this->hasTabs );
			$template->setVar( 'namespace', $this->globals->nameSpace );
			$template->setVar( 'title', $this->pageTitle );
			$template->setVar( 'version', $this->globals->get( 'version' ) );
			$template->setVar( 'exported_settings', $this->globals->get( 'exported_settings' ) );

			// construct the intructions URI
			$pluginDirName = $this->globals->get( 'pluginDirName' );
			$pluginDirName = str_replace( 'wp-' , 'weepie-', $pluginDirName );
			$instructionsUri = esc_url( sprintf( "https://www.weepie-plugins.com/instruction-guide-%s-plugin/?utm_source=%s_plugin&utm_medium=wp_admin&utm_campaign=menu_%s", $pluginDirName, $this->globals->nameSpace, $tabidx ) );

			if( $this->hasTabs ) {
				$template->setVar( 'tabs', $this->tabs );
			}

			$template->setVar( 'instructions_uri', $instructionsUri );

			// create new WpieTemplate instance
			$finder = new WpieTemplatesFileFinder( $this->templatePath, '', '', self::TEMPL_FILE_NAME_SETTINGS_PAGE_TABLE, false );
			$templateTable = new WpieTemplate( $finder, 'table' );

			// setup Template vars
			$templateTable->setVars( $this->settings[$tabidx]->getstack() );
			$templateTable->setVar( 'current_templ', $tabidx );

			$templateTable->setVar( 'setting', $setting );
			$templateTable->setVar( 'locale', $this->globals->get('locale') );
			$templateTable->setVar( 'module_path', $this->globals->get( 'modulePath' ) );
			$templateTable->setVar( 'form_fields', $this->settings[$tabidx]->getArray( $tabidx ) );
			$templateTable->setVar( 'do_submit_btn', true );
			$templateTable->setVar( 'namespace', $this->globals->nameSpace );
			$vars = $templateTable->getVars();

			/**
			 * This hook let other Modules / Plugins filter the Template vars
			 *
			 * @param array $vars Template vars
			 */
			$vars = apply_filters( $this->globals->nameSpace . '_templ_vars', $vars, $tabidx );

			// add vars again in case $vars is modified
			$templateTable->setVars( $vars );

			// render
			$table = $templateTable->render( false, true );
			$template->setVar( 'table', $table );

			// render
			echo $template->render();
		} catch( WpieExceptionInterface $e ) {
			// @todo show exceptions in a more nicer way
			WpieExceptionLogger::log( $e );
			wp_die( $e->getMessage() );
		} catch( \Throwable $e ) {
			WpieExceptionLogger::log( $e );
			wp_die( $e->getMessage() );
		}
	}

	/**
	 * Callback for the add_menu_page and add_submenu_page functions
	 *
	 * @access public
	 *
	 * @uses WpiePluginSettingsPage::render()
	 *
	 * @since 1.2
	 */
	public function showPage() {

		$this->render();
	}

	/**
	 * Callback for the {namespace}_add_admin_pages hook
	 *
	 * Main tasks are:
	 * 	- finish populating the '_tabs' array
	 * 	- add the settings pages
	 * 	- set some class members
	 *
	 * @access public
	 *
	 * @uses self::addPageSingle() OR
	 * @uses self::addPageTabs()
	 *
	 * @since 1.2
	 */
	public function addPage()
	{
		$this->defaultTab = $this->getDefaultTab();
		$this->defaultTabIdx = $this->getDefaultTabIdx();

		if( 1 === count( $this->tabs ) ) {
			$this->addPageSingle();
		} else {
			$this->hasTabs = true;
			$this->addPageTabs();
		}
	}

	/**
	 * Callback for the plugin_action_links_{$pluginFile} hook
	 *
	 * Add a link to the settings page to the plugin actions
	 *
	 * @access public
	 *
	 * @param array $actions
	 *
	 * @since 1.2
	 *
	 * @return array
	 */
	public function addPluginActionLink( $actions )
	{
		$actions['settings'] = sprintf( '<a href="%s" title="%s">%s</a>', $this->uri, __( $this->pageTitle, $this->globals->nameSpace ), __( 'Settings', $this->globals->nameSpace ) );
		return $actions;
	}

	/**
	 * Handles the init logic
	 *
	 * @access public
	 *
	 * @param WpieSettingsCollection $settings
	 *
	 * @uses self::setTabs()
	 */
	public function init( WpieSettingsCollection $settings )
	{
		$this->settings = $settings;
		$this->setTabs();
	}

	/**
	 * Add data to the tabs array for given tab
	 *
	 * @access private
	 *
	 * @param string $key the index of the tab in the array
	 *
	 * @since 1.2
	 */
	private function addTabData( $key )
	{
		$slug = sprintf( $this->slug, $this->globals->nameSpace );
		$slug = sprintf( '%s-%s', $slug, $key );

		$this->tabs[$key]['idx'] = $key;
		$this->tabs[$key]['slug'] = $slug;
		$this->tabs[$key]['class'] = ( $this->currentTabSlug === $slug ) ? 'tab-active' : 'tab-in-active';
		$this->tabs[$key]['uri'] = admin_url( sprintf('admin.php?page=%s', $slug ) );
	}

	/**
	 *
	 */
	private function setTabs()
	{
		foreach ( $this->settings as $key => $v ) {
			if( isset( $this->tabs[$key] ) ) {
				continue;
			}

			$prio = (int) $this->settings[$key]->getPriority();
			$default = $this->settings[$key]->getIsDefault();
			$title = __( ucwords( preg_replace( '/(_)/', ' ', $key ) ), $this->globals->nameSpace );
			$this->tabs[$key] = [ 'setting' => '', 'tab' => $title, 'default' => $default, 'prio' => $prio ];
		}

		// sort the array based on the priority
		uasort( $this->tabs, [ $this, 'sortTabs' ] );

		foreach ( $this->tabs as $key => $tab ) {
			$this->addTabData( $key );
		}
	}

	/**
	 * Callback for the uasort function
	 *
	 * Sort the array based on the "prio" entry in the array
	 *
	 * @acces	private
	 *
	 * @param 	array	$wee
	 * @param 	array	$pie
	 *
	 * @since 	1.2
	 *
	 * @return 	number
	 */
	private function sortTabs( $wee, $pie )
	{
		if ($wee['prio'] == $pie['prio']) {
			return 0;
		}

		return ($wee['prio'] < $pie['prio']) ? -1 : 1;
	}

	/**
	 * Unset tabs
	 *
	 * @access private
	 *
	 * @since 1.4.0
	 */
	private function unsetTabs()
	{
		$this->tabs = [];
		$this->hasTabs = false;
	}

	/**
	 * Add the settings page for one tab
	 *
	 * @access private
	 *
	 * @uses add_options_page()
	 * @uses admin_url()
	 *
	 * @since 1.2
	 */
	private function addPageSingle()
	{
		$slug = sprintf( '%s-%s', $this->slug, $this->defaultTabIdx );
		$this->hooknames[] = add_options_page( $this->pageTitle,  $this->menuTitle, 'manage_options', $slug, [ $this, 'showPage' ] );
		$this->uri = admin_url( sprintf( 'options-general.php?page=%s', $slug ) );
	}

	/**
	 * Add the settings page for multiple tabs
	 *
	 * @access private
	 *
	 * @uses add_menu_page()
	 * @uses add_submenu_page()
	 *
	 * @since 1.2
	 */
	private function addPageTabs()
	{
		$this->parentSlug = sprintf( '%s-%s', $this->slug, $this->defaultTabIdx );

		foreach ($this->tabs as $key => $tab ) {
			if( $this->defaultTabIdx === $key ) {
				$tabDefault = $this->tabs[$key];
				unset( $this->tabs[$key] );
				$this->tabs = [ $this->defaultTabIdx => $tabDefault ] + $this->tabs;
				break;
			}
		}

		foreach( $this->tabs as $key => $tab ) {
			$slug = $tab['slug'];
			$title = ( $this->defaultTabIdx === $key ) ? $this->pageTitle : $tab['tab'];
			$menuTitle = ( $this->defaultTabIdx === $key ) ? $this->menuTitle : $tab['tab'];

			if( $this->defaultTabIdx === $key ) {
				$this->tabs[$key]['hookname'] = add_menu_page( $title, $menuTitle, 'manage_options', $slug, [ $this, 'showPage' ], $this->globals->get( 'imgUri' ) . '/icon.svg' );
			} else {
				$this->tabs[$key]['hookname'] = add_submenu_page( $this->parentSlug, $title,  $menuTitle, 'manage_options', $slug, [ $this, 'showPage' ] );
			}

			if( $key === $this->defaultTabIdx ) {
				$this->uriTabDefault = $this->uri = $this->tabs[$key]['uri'];
			}

			$this->hooknames[$key] = $this->tabs[$key]['hookname'];
		}

		// Rename menu-item general to general instead of self::menuTitle
		global $submenu;
		if ( isset( $submenu[$this->tabs[$this->defaultTabIdx]['slug']] ) ) {
			$submenu[$this->tabs[$this->defaultTabIdx]['slug']][0][0] = $this->tabs[$this->defaultTabIdx]['tab'];
		}
	}
}