// Types
import { TabContentItem } from '../tab-navigation-base/tab-content-item';
import { TabStrip } from '../tab-navigation-base/tab-strip';
import { TabStripItem } from '../tab-navigation-base/tab-strip-item';
import { TextTransform } from '../text-base';

// Requires
import { Color } from '../../color';
import { ImageSource } from '../../image-source';
import { Device } from '../../platform';
import { iOSNativeHelper, isFontIconURI, layout } from '../../utils';
import { IOSHelper, View } from '../core/view';
import { ViewBase } from '../core/view-base';
import { Frame } from '../frame';
import { Font } from '../styling/font';
import { getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty } from '../tab-navigation-base/tab-navigation-base';
import { swipeEnabledProperty, TabsBase, IOSTabBarItemsAlignment, iOSTabBarItemsAlignmentProperty } from './tabs-common';

// TODO
// import { profile } from "../../profiling";

export * from './tabs-common';

const majorVersion = iOSNativeHelper.MajorVersion;
const isPhone = Device.deviceType === 'Phone';

// Equivalent to dispatch_async(dispatch_get_main_queue(...)) call
const invokeOnRunLoop = (function () {
	const runloop = CFRunLoopGetMain();

	return (action: () => any) => {
		CFRunLoopPerformBlock(runloop, kCFRunLoopDefaultMode, action);
		CFRunLoopWakeUp(runloop);
	};
})();

@NativeClass
class MDCTabBarDelegateImpl extends NSObject implements MDCTabBarDelegate {
	public static ObjCProtocols = [MDCTabBarDelegate];

	private _owner: WeakRef<Tabs>;

	public static initWithOwner(owner: WeakRef<Tabs>): MDCTabBarDelegateImpl {
		const delegate = <MDCTabBarDelegateImpl>MDCTabBarDelegateImpl.new();
		delegate._owner = owner;

		return delegate;
	}

	public tabBarShouldSelectItem(tabBar: MDCTabBar, item: UITabBarItem): boolean {
		const owner = this._owner.get();
		const shouldSelectItem = owner._canSelectItem;
		const selectedIndex = owner.tabBarItems.indexOf(item);

		if (owner.selectedIndex !== selectedIndex) {
			owner._canSelectItem = false;
		}

		const tabStrip = owner.tabStrip;
		const tabStripItems = tabStrip && tabStrip.items;

		if (tabStripItems && tabStripItems[selectedIndex]) {
			tabStripItems[selectedIndex]._emit(TabStripItem.tapEvent);
			tabStrip.notify({
				eventName: TabStrip.itemTapEvent,
				object: tabStrip,
				index: selectedIndex,
			});
		}

		return shouldSelectItem;
	}

	public tabBarDidSelectItem(tabBar: MDCTabBar, selectedItem: UITabBarItem): void {
		const owner = this._owner.get();
		const tabBarItems = owner.tabBarItems;
		const selectedIndex = tabBarItems.indexOf(selectedItem);

		owner.selectedIndex = selectedIndex;
	}
}

@NativeClass
class BackgroundIndicatorTemplate extends NSObject implements MDCTabBarIndicatorTemplate {
	public static ObjCProtocols = [MDCTabBarIndicatorTemplate];

	public indicatorAttributesForContext(context: MDCTabBarIndicatorContext): MDCTabBarIndicatorAttributes {
		const attributes = new MDCTabBarIndicatorAttributes();
		attributes.path = UIBezierPath.bezierPathWithRect(context.bounds);

		return attributes;
	}
}

@NativeClass
class UIPageViewControllerImpl extends UIPageViewController {
	tabBar: MDCTabBar;
	scrollView: UIScrollView;
	tabBarDelegate: MDCTabBarDelegateImpl;

	private _owner: WeakRef<Tabs>;

	public static initWithOwner(owner: WeakRef<Tabs>): UIPageViewControllerImpl {
		const handler = <UIPageViewControllerImpl>UIPageViewControllerImpl.alloc().initWithTransitionStyleNavigationOrientationOptions(UIPageViewControllerTransitionStyle.Scroll, UIPageViewControllerNavigationOrientation.Horizontal, null);
		handler._owner = owner;

		return handler;
	}

	public viewDidLoad(): void {
		const owner = this._owner.get();

		const tabBarItems = owner.tabBarItems;
		const tabBar = MDCTabBar.alloc().initWithFrame(this.view.bounds);

		if (tabBarItems && tabBarItems.length) {
			tabBar.items = NSArray.arrayWithArray(tabBarItems);
		}

		tabBar.delegate = this.tabBarDelegate = MDCTabBarDelegateImpl.initWithOwner(new WeakRef(owner));

		if (majorVersion <= 12 || !UIColor.labelColor) {
			tabBar.tintColor = UIColor.blueColor;
			tabBar.barTintColor = UIColor.whiteColor;
			tabBar.setTitleColorForState(UIColor.blackColor, MDCTabBarItemState.Normal);
			tabBar.setTitleColorForState(UIColor.blackColor, MDCTabBarItemState.Selected);
		} else {
			tabBar.tintColor = UIColor.systemBlueColor;
			tabBar.barTintColor = UIColor.systemBackgroundColor;
			tabBar.setTitleColorForState(UIColor.labelColor, MDCTabBarItemState.Normal);
			tabBar.setTitleColorForState(UIColor.labelColor, MDCTabBarItemState.Selected);
			tabBar.inkColor = UIColor.clearColor;
		}

		tabBar.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleBottomMargin;
		tabBar.alignment = MDCTabBarAlignment.Justified;
		tabBar.sizeToFit();

		this.tabBar = tabBar;
		this.view.addSubview(tabBar);
	}

	public viewWillAppear(animated: boolean): void {
		super.viewWillAppear(animated);
		const owner = this._owner.get();
		if (!owner) {
			return;
		}

		IOSHelper.updateAutoAdjustScrollInsets(this, owner);

		// Tabs can be reset as a root view. Call loaded here in this scenario.
		if (!owner.isLoaded) {
			owner.callLoaded();
		}
	}

	public viewDidLayoutSubviews(): void {
		super.viewDidLayoutSubviews();
		const owner = this._owner.get();
		if (!owner) {
			return;
		}

		let safeAreaInsetsBottom = 0;
		let safeAreaInsetsTop = 0;

		if (majorVersion > 10) {
			safeAreaInsetsBottom = this.view.safeAreaInsets.bottom;
			safeAreaInsetsTop = this.view.safeAreaInsets.top;
		} else {
			safeAreaInsetsTop = this.topLayoutGuide.length;
		}

		let conditionalSafeAreaBottom = owner.iosOverflowSafeArea ? safeAreaInsetsBottom : 0;
		let scrollViewTop = 0;
		let scrollViewHeight = this.view.bounds.size.height + conditionalSafeAreaBottom;

		if (owner.tabStrip) {
			const tabBarHeight = this.tabBar.frame.size.height;
			let tabBarTop = safeAreaInsetsTop;

			scrollViewTop = tabBarHeight;
			scrollViewHeight = this.view.bounds.size.height - tabBarHeight + conditionalSafeAreaBottom;

			const tabsPosition = owner.tabsPosition;
			if (tabsPosition === 'bottom') {
				tabBarTop = this.view.frame.size.height - tabBarHeight - safeAreaInsetsBottom;
				scrollViewTop = this.view.frame.origin.y;
				scrollViewHeight = this.view.frame.size.height - tabBarHeight;
			}

			let parent = owner.parent;

			// Handle Angular scenario where Tabs is in a ProxyViewContainer
			// It is possible to wrap components in ProxyViewContainers indefinitely
			while (parent && !parent.nativeViewProtected) {
				parent = parent.parent;
			}

			if (parent && majorVersion > 10) {
				// TODO: Figure out a better way to handle ViewController nesting/Safe Area nesting
				tabBarTop = Math.max(tabBarTop, parent.nativeView.safeAreaInsets.top);
			}

			this.tabBar.frame = CGRectMake(0, tabBarTop, this.tabBar.frame.size.width, tabBarHeight);
		} else {
			this.tabBar.hidden = true;
		}

		const subViews: NSArray<UIView> = this.view.subviews;
		let scrollView: UIScrollView = null;

		for (let i = 0; i < subViews.count; i++) {
			const view: UIView = subViews[i];
			if (view instanceof UIScrollView) {
				scrollView = <UIScrollView>view;
			}
		}

		if (scrollView) {
			// The part of the UIPageViewController that is changing the pages is a UIScrollView
			// We want to expand it to the size of the UIPageViewController as it is not so by default
			this.scrollView = scrollView;

			if (!owner.swipeEnabled) {
				scrollView.scrollEnabled = false;
			}

			scrollView.frame = CGRectMake(0, scrollViewTop, this.view.bounds.size.width, scrollViewHeight); //this.view.bounds;
		}
	}

	// Mind implementation for other controllers
	public traitCollectionDidChange(previousTraitCollection: UITraitCollection): void {
		super.traitCollectionDidChange(previousTraitCollection);

		if (majorVersion >= 13) {
			const owner = this._owner.get();
			if (owner && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(previousTraitCollection)) {
				owner.notify({
					eventName: IOSHelper.traitCollectionColorAppearanceChangedEvent,
					object: owner,
				});
			}
		}
	}

	public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void {
		super.viewWillTransitionToSizeWithTransitionCoordinator(size, coordinator);
		coordinator.animateAlongsideTransitionCompletion(() => {
			const owner = this._owner.get();
			if (owner && owner.tabStrip && owner.tabStrip.items) {
				const tabStrip = owner.tabStrip;
				tabStrip.items.forEach((tabStripItem) => {
					updateBackgroundPositions(tabStrip, tabStripItem, this.tabBar.alignment !== MDCTabBarAlignment.Justified || owner.selectedIndex !== tabStripItem._index ? owner._defaultItemBackgroundColor : null);

					const index = tabStripItem._index;
					const tabBarItemController = owner.viewControllers[index];
					updateTitleAndIconPositions(tabStripItem, tabBarItemController.tabBarItem, tabBarItemController);
				});
			}
		}, null);
	}
}

@NativeClass
class UIPageViewControllerDataSourceImpl extends NSObject implements UIPageViewControllerDataSource {
	public static ObjCProtocols = [UIPageViewControllerDataSource];

	private _owner: WeakRef<Tabs>;

	public static initWithOwner(owner: WeakRef<Tabs>): UIPageViewControllerDataSourceImpl {
		const dataSource = <UIPageViewControllerDataSourceImpl>UIPageViewControllerDataSourceImpl.new();
		dataSource._owner = owner;

		return dataSource;
	}

	public pageViewControllerViewControllerBeforeViewController(pageViewController: UIPageViewController, viewController: UIViewController): UIViewController {
		// TODO
		// if (Trace.isEnabled()) {
		//     Trace.write("TabView.delegate.SHOULD_select(" + tabBarController + ", " + viewController + ");", Trace.categories.Debug);
		// }

		const owner = this._owner.get();
		let selectedIndex = owner.selectedIndex;

		if (selectedIndex === 0) {
			return null;
		}

		selectedIndex--;
		const prevItem = owner.items[selectedIndex];
		const prevViewController = (<any>prevItem).__controller;

		// if (!prevViewController) {
		//     prevViewController = owner.getViewController(prevItem);
		// }

		owner._setCanBeLoaded(selectedIndex);
		owner._loadUnloadTabItems(selectedIndex);

		return prevViewController;
	}

	public pageViewControllerViewControllerAfterViewController(pageViewController: UIPageViewController, viewController: UIViewController): UIViewController {
		// TODO
		// if (Trace.isEnabled()) {
		//     Trace.write("TabView.delegate.SHOULD_select(" + tabBarController + ", " + viewController + ");", Trace.categories.Debug);
		// }

		const owner = this._owner.get();
		let selectedIndex = owner.selectedIndex;

		if (selectedIndex === owner.items.length - 1) {
			return null;
		}

		selectedIndex++;
		const nextItem = owner.items[selectedIndex];
		const nextViewController = (<any>nextItem).__controller;

		// if (!nextViewController) {
		//     nextViewController = owner.getViewController(nextItem);
		// }

		owner._setCanBeLoaded(selectedIndex);
		owner._loadUnloadTabItems(selectedIndex);
		// nextItem.loadView(nextItem.view);

		return nextViewController;
	}

	public presentationCountForPageViewController(pageViewController: UIPageViewController): number {
		// TODO
		// if (Trace.isEnabled()) {
		//     Trace.write("TabView.delegate.SHOULD_select(" + tabBarController + ", " + viewController + ");", Trace.categories.Debug);
		// }

		return 0;
	}

	public presentationIndexForPageViewController(pageViewController: UIPageViewController): number {
		// TODO
		// if (Trace.isEnabled()) {
		//     Trace.write("TabView.delegate.SHOULD_select(" + tabBarController + ", " + viewController + ");", Trace.categories.Debug);
		// }

		return 0;
	}
}

@NativeClass
class UIPageViewControllerDelegateImpl extends NSObject implements UIPageViewControllerDelegate {
	public static ObjCProtocols = [UIPageViewControllerDelegate];

	private _owner: WeakRef<Tabs>;

	public static initWithOwner(owner: WeakRef<Tabs>): UIPageViewControllerDelegateImpl {
		const delegate = <UIPageViewControllerDelegateImpl>UIPageViewControllerDelegateImpl.new();
		delegate._owner = owner;

		return delegate;
	}

	public pageViewControllerWillTransitionToViewControllers(pageViewController: UIPageViewController, viewControllers: NSArray<UIViewController>): void {
		// const owner = this._owner.get();
		// const ownerViewControllers = owner.viewControllers;
		// const selectedIndex = owner.selectedIndex;
		// const nextViewController = viewControllers[0];
		// const nextViewControllerIndex = ownerViewControllers.indexOf(nextViewController);
		// if (selectedIndex > nextViewControllerIndex) {
		//     owner.selectedIndex--;
		// } else {
		//     owner.selectedIndex++;
		// }
	}

	public pageViewControllerDidFinishAnimatingPreviousViewControllersTransitionCompleted(pageViewController: UIPageViewController, didFinishAnimating: boolean, previousViewControllers: NSArray<UIViewController>, transitionCompleted: boolean): void {
		if (!transitionCompleted) {
			return;
		}

		const owner = this._owner.get();
		const ownerViewControllers = owner.viewControllers;
		const selectedIndex = owner.selectedIndex;
		const nextViewController = pageViewController.viewControllers[0];
		const nextViewControllerIndex = ownerViewControllers.indexOf(nextViewController);

		if (selectedIndex !== nextViewControllerIndex) {
			owner.selectedIndex = nextViewControllerIndex;
			owner._canSelectItem = true;
		}
	}
}

function iterateIndexRange(index: number, eps: number, lastIndex: number, callback: (i) => void) {
	const rangeStart = Math.max(0, index - eps);
	const rangeEnd = Math.min(index + eps, lastIndex);
	for (let i = rangeStart; i <= rangeEnd; i++) {
		callback(i);
	}
}

function updateBackgroundPositions(tabStrip: TabStrip, tabStripItem: TabStripItem, color: UIColor = null) {
	let bgView = (<any>tabStripItem).bgView;
	const index = tabStripItem._index;
	const width = tabStrip.nativeView.frame.size.width / tabStrip.items.length;
	const frame = CGRectMake(width * index, 0, width, tabStrip.nativeView.frame.size.width);
	if (!bgView) {
		bgView = UIView.alloc().initWithFrame(frame);
		tabStrip.nativeView.insertSubviewAtIndex(bgView, 0);
		(<any>tabStripItem).bgView = bgView;
	} else {
		bgView.frame = frame;
	}

	const backgroundColor = tabStripItem.style.backgroundColor;
	bgView.backgroundColor = color || (backgroundColor instanceof Color ? backgroundColor.ios : backgroundColor);
}

function updateTitleAndIconPositions(tabStripItem: TabStripItem, tabBarItem: UITabBarItem, controller: UIViewController) {
	if (!tabStripItem || !tabBarItem) {
		return;
	}

	// For iOS <11 icon is *always* above the text.
	// For iOS 11 icon is above the text *only* on phones in portrait mode.
	const orientation = controller.interfaceOrientation;
	const isPortrait = orientation !== UIInterfaceOrientation.LandscapeLeft && orientation !== UIInterfaceOrientation.LandscapeRight;
	const isIconAboveTitle = majorVersion < 11 || (isPhone && isPortrait);

	if (!tabStripItem.iconSource) {
		if (isIconAboveTitle) {
			tabBarItem.titlePositionAdjustment = {
				horizontal: 0,
				vertical: -20,
			};
		} else {
			tabBarItem.titlePositionAdjustment = { horizontal: 0, vertical: 0 };
		}
	}

	if (!tabStripItem.title) {
		if (isIconAboveTitle) {
			tabBarItem.imageInsets = new UIEdgeInsets({
				top: 6,
				left: 0,
				bottom: -6,
				right: 0,
			});
		} else {
			tabBarItem.imageInsets = new UIEdgeInsets({
				top: 0,
				left: 0,
				bottom: 0,
				right: 0,
			});
		}
	}
}

export class Tabs extends TabsBase {
	public nativeViewProtected: UIView;
	public selectedIndex: number;
	// public swipeEnabled: boolean;
	// public offscreenTabLimit: number;
	// public tabsPosition: "top" | "bottom";
	public _canSelectItem: boolean;
	// @ts-ignore
	public isLoaded: boolean;
	public viewController: UIPageViewControllerImpl;
	public items: TabContentItem[];
	public _ios: UIPageViewControllerImpl;
	public viewControllers: UIViewController[];
	public tabBarItems: UITabBarItem[];
	private _currentNativeSelectedIndex: number;
	private _dataSource: UIPageViewControllerDataSourceImpl;
	private _delegate: UIPageViewControllerDelegateImpl;
	// private _moreNavigationControllerDelegate: UINavigationControllerDelegateImpl;
	private _iconsCache = {};
	private _backgroundIndicatorColor: UIColor;
	public _defaultItemBackgroundColor: UIColor;
	private _selectedItemColor: Color;
	private _unSelectedItemColor: Color;
	public animationEnabled: boolean;

	constructor() {
		super();

		this.viewController = this._ios = <UIPageViewControllerImpl>UIPageViewControllerImpl.initWithOwner(new WeakRef(this)); //alloc().initWithTransitionStyleNavigationOrientationOptions(UIPageViewControllerTransitionStyle.Scroll, UIPageViewControllerNavigationOrientation.Horizontal, null);;
	}

	createNativeView() {
		return this._ios.view;
	}

	initNativeView() {
		super.initNativeView();
		this._dataSource = UIPageViewControllerDataSourceImpl.initWithOwner(new WeakRef(this));
		this._delegate = UIPageViewControllerDelegateImpl.initWithOwner(new WeakRef(this));
	}

	disposeNativeView() {
		this._dataSource = null;
		this._delegate = null;
		this._ios.tabBarDelegate = null;
		this._ios.tabBar = null;
		super.disposeNativeView();
	}

	// TODO
	// @profile()
	public onLoaded() {
		super.onLoaded();

		this.setViewControllers(this.items);

		const selectedIndex = this.selectedIndex;
		const selectedView = this.items && this.items[selectedIndex] && this.items[selectedIndex].content;
		if (selectedView instanceof Frame) {
			selectedView._pushInFrameStackRecursive();
		}

		this._ios.dataSource = this._dataSource;
		this._ios.delegate = this._delegate;
	}

	public onUnloaded() {
		this._ios.dataSource = null;
		this._ios.delegate = null;
		super.onUnloaded();
	}

	// @ts-ignore
	get ios(): UIPageViewController {
		return this._ios;
	}

	public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
		//
	}

	public _setNativeViewFrame(nativeView: UIView, frame: CGRect) {
		//
	}

	public onSelectedIndexChanged(oldIndex: number, newIndex: number): void {
		const items = this.items;
		if (!items) {
			return;
		}

		const oldItem = items[oldIndex];
		if (oldItem) {
			oldItem.canBeLoaded = false;
			oldItem.unloadView(oldItem.content);
		}

		const newItem = items[newIndex];
		if (newItem && this.isLoaded) {
			const selectedView = items[newIndex].content;
			if (selectedView instanceof Frame) {
				selectedView._pushInFrameStackRecursive();
			}

			newItem.canBeLoaded = true;
			newItem.loadView(newItem.content);
		}

		const tabStripItems = this.tabStrip && this.tabStrip.items;
		if (tabStripItems) {
			if (tabStripItems[newIndex]) {
				tabStripItems[newIndex]._emit(TabStripItem.selectEvent);
				this.updateItemColors(tabStripItems[newIndex]);
			}

			if (tabStripItems[oldIndex]) {
				tabStripItems[oldIndex]._emit(TabStripItem.unselectEvent);
				this.updateItemColors(tabStripItems[oldIndex]);
			}
		}

		this._loadUnloadTabItems(newIndex);

		super.onSelectedIndexChanged(oldIndex, newIndex);
	}

	public _loadUnloadTabItems(newIndex: number) {
		const items = this.items;
		if (!items) {
			return;
		}

		const lastIndex = items.length - 1;
		const offsideItems = this.offscreenTabLimit;

		const toUnload = [];
		const toLoad = [];

		iterateIndexRange(newIndex, offsideItems, lastIndex, (i) => toLoad.push(i));

		items.forEach((item, i) => {
			const indexOfI = toLoad.indexOf(i);
			if (indexOfI < 0) {
				toUnload.push(i);
			}
		});

		toUnload.forEach((index) => {
			const item = items[index];
			if (items[index]) {
				item.unloadView(item.content);
			}
		});

		const newItem = items[newIndex];
		const selectedView = newItem && newItem.content;
		if (selectedView instanceof Frame) {
			selectedView._pushInFrameStackRecursive();
		}

		toLoad.forEach((index) => {
			const item = items[index];
			if (this.isLoaded && items[index]) {
				item.loadView(item.content);
			}
		});
	}

	public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
		const width = layout.getMeasureSpecSize(widthMeasureSpec);
		const widthMode = layout.getMeasureSpecMode(widthMeasureSpec);

		const height = layout.getMeasureSpecSize(heightMeasureSpec);
		const heightMode = layout.getMeasureSpecMode(heightMeasureSpec);

		const widthAndState = View.resolveSizeAndState(width, width, widthMode, 0);
		const heightAndState = View.resolveSizeAndState(height, height, heightMode, 0);

		this.setMeasuredDimension(widthAndState, heightAndState);
	}

	public _onViewControllerShown(viewController: UIViewController) {
		// This method could be called with the moreNavigationController or its list controller, so we have to check.
		// TODO
		// if (Trace.isEnabled()) {
		//     Trace.write("TabView._onViewControllerShown(" + viewController + ");", Trace.categories.Debug);
		// }
		if (this._ios.viewControllers && this._ios.viewControllers.containsObject(viewController)) {
			this.selectedIndex = this._ios.viewControllers.indexOfObject(viewController);
		} else {
			// TODO
			// if (Trace.isEnabled()) {
			//     Trace.write("TabView._onViewControllerShown: viewController is not one of our viewControllers", Trace.categories.Debug);
			// }
		}
	}

	private getViewController(item: TabContentItem): UIViewController {
		let newController: UIViewController = item.content ? item.content.viewController : null;

		if (newController) {
			(<any>item).setViewController(newController, newController.view);

			return newController;
		}

		if (item.content.ios instanceof UIViewController) {
			newController = item.content.ios;
			(<any>item).setViewController(newController, newController.view);
		} else if (item.content.ios && item.content.ios.controller instanceof UIViewController) {
			newController = item.content.ios.controller;
			(<any>item).setViewController(newController, newController.view);
		} else {
			newController = IOSHelper.UILayoutViewController.initWithOwner(new WeakRef(item.content)) as UIViewController;
			newController.view.addSubview(item.content.nativeViewProtected);
			item.content.viewController = newController;
			(<any>item).setViewController(newController, item.content.nativeViewProtected);
		}

		return newController;
	}

	public _setCanBeLoaded(index: number) {
		const items = this.items;
		if (!this.items) {
			return;
		}

		const lastIndex = items.length - 1;
		const offsideItems = this.offscreenTabLimit;

		iterateIndexRange(index, offsideItems, lastIndex, (i) => {
			if (items[i]) {
				(<TabContentItem>items[i]).canBeLoaded = true;
			}
		});
	}

	private setViewControllers(items: TabContentItem[]) {
		const length = items ? items.length : 0;
		if (length === 0) {
			this.viewControllers = null;

			return;
		}

		const viewControllers = [];
		const tabBarItems = [];

		if (this.tabStrip) {
			this.tabStrip.setNativeView(this._ios.tabBar);
		}

		const tabStripItems = this.tabStrip && this.tabStrip.items;
		if (tabStripItems) {
			if (tabStripItems[this.selectedIndex]) {
				tabStripItems[this.selectedIndex]._emit(TabStripItem.selectEvent);
			}
		}

		items.forEach((item, i) => {
			const controller = this.getViewController(item);

			if (this.tabStrip && this.tabStrip.items && this.tabStrip.items[i]) {
				const tabStripItem = <TabStripItem>this.tabStrip.items[i];
				const tabBarItem = this.createTabBarItem(tabStripItem, i);
				updateTitleAndIconPositions(tabStripItem, tabBarItem, controller);

				this.setViewTextAttributes(tabStripItem.label, i === this.selectedIndex);

				controller.tabBarItem = tabBarItem;
				tabStripItem._index = i;
				tabBarItems.push(tabBarItem);
				tabStripItem.setNativeView(tabBarItem);
			}

			item.canBeLoaded = true;
			viewControllers.push(controller);
		});

		this.setItemImages();

		this.viewControllers = viewControllers;
		this.tabBarItems = tabBarItems;

		if (this.viewController && this.viewController.tabBar) {
			this.viewController.tabBar.itemAppearance = this.getTabBarItemAppearance();
			this.viewController.tabBar.items = NSArray.arrayWithArray(this.tabBarItems);
			// TODO: investigate why this call is necessary to actually toggle item appearance
			this.viewController.tabBar.sizeToFit();
			if (this.selectedIndex) {
				this.viewController.tabBar.setSelectedItemAnimated(this.tabBarItems[this.selectedIndex], false);
			}
		}
	}

	private setItemImages() {
		if (this._selectedItemColor || this._unSelectedItemColor) {
			if (this.tabStrip && this.tabStrip.items) {
				this.tabStrip.items.forEach((item) => {
					if (this._unSelectedItemColor && item.nativeView) {
						item.nativeView.image = this.getIcon(item, this._unSelectedItemColor);
					}
					if (this._selectedItemColor && item.nativeView) {
						if (this.selectedIndex === item._index) {
							item.nativeView.image = this.getIcon(item, this._selectedItemColor);
						}
					}
				});
			}
		}
	}

	private updateAllItemsColors() {
		this._defaultItemBackgroundColor = null;
		this.setItemColors();
		if (this.tabStrip && this.tabStrip.items) {
			this.tabStrip.items.forEach((tabStripItem) => {
				this.updateItemColors(tabStripItem);
			});
		}
	}

	private updateItemColors(tabStripItem: TabStripItem): void {
		updateBackgroundPositions(this.tabStrip, tabStripItem);
		this.setIconColor(tabStripItem, true);
	}

	private createTabBarItem(item: TabStripItem, index: number): UITabBarItem {
		let image: UIImage;
		let title: string;

		if (item.isLoaded) {
			image = this.getIcon(item);
			title = item.label.text;

			if (!this.tabStrip._hasImage) {
				this.tabStrip._hasImage = !!image;
			}

			if (!this.tabStrip._hasTitle) {
				this.tabStrip._hasTitle = !!title;
			}
		}

		const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag(title, image, index);

		return tabBarItem;
	}

	private getTabBarItemAppearance(): MDCTabBarItemAppearance {
		let itemAppearance;
		if (this.tabStrip && this.tabStrip._hasImage && this.tabStrip._hasTitle) {
			itemAppearance = MDCTabBarItemAppearance.TitledImages;
		} else if (this.tabStrip && this.tabStrip._hasImage) {
			itemAppearance = MDCTabBarItemAppearance.Images;
		} else {
			itemAppearance = MDCTabBarItemAppearance.Titles;
		}

		return itemAppearance;
	}

	private getIconRenderingMode(): UIImageRenderingMode {
		switch (this.tabStrip && this.tabStrip.iosIconRenderingMode) {
			case 'alwaysOriginal':
				return UIImageRenderingMode.AlwaysOriginal;
			case 'alwaysTemplate':
				return UIImageRenderingMode.AlwaysTemplate;
			case 'automatic':
			default:
				const hasItemColor = this._selectedItemColor || this._unSelectedItemColor;

				return hasItemColor ? UIImageRenderingMode.AlwaysTemplate : UIImageRenderingMode.AlwaysOriginal;
		}
	}

	private getIcon(tabStripItem: TabStripItem, color?: Color): UIImage {
		// Image and Label children of TabStripItem
		// take priority over its `iconSource` and `title` properties
		const iconSource = tabStripItem.image && tabStripItem.image.src;
		if (!iconSource) {
			return null;
		}

		const target = tabStripItem.image;
		const font = target.style.fontInternal || Font.default;
		if (!color) {
			color = target.style.color;
		}
		const iconTag = [iconSource, font.fontStyle, font.fontWeight, font.fontSize, font.fontFamily, color].join(';');

		let isFontIcon = false;
		let image: UIImage = this._iconsCache[iconTag];
		if (!image) {
			let is = new ImageSource();
			if (isFontIconURI(iconSource)) {
				isFontIcon = true;
				const fontIconCode = iconSource.split('//')[1];
				is = ImageSource.fromFontIconCodeSync(fontIconCode, font, color);
			} else {
				is = ImageSource.fromFileOrResourceSync(iconSource);
			}

			if (is && is.ios) {
				image = is.ios;

				if (this.tabStrip && this.tabStrip.isIconSizeFixed) {
					image = this.getFixedSizeIcon(image);
				}

				let renderingMode: UIImageRenderingMode = UIImageRenderingMode.Automatic;
				if (!isFontIcon) {
					renderingMode = this.getIconRenderingMode();
				}
				const originalRenderedImage = image.imageWithRenderingMode(renderingMode);
				this._iconsCache[iconTag] = originalRenderedImage;
				image = originalRenderedImage;
			}
		}

		return image;
	}

	private getFixedSizeIcon(image: UIImage): UIImage {
		const inWidth = image.size.width;
		const inHeight = image.size.height;

		const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight });

		const widthPts = iconSpecSize.width;
		const heightPts = iconSpecSize.height;

		UIGraphicsBeginImageContextWithOptions({ width: widthPts, height: heightPts }, false, layout.getDisplayDensity());
		image.drawInRect(CGRectMake(0, 0, widthPts, heightPts));
		const resultImage = UIGraphicsGetImageFromCurrentImageContext();
		UIGraphicsEndImageContext();

		return resultImage;
	}

	public getTabBarBackgroundColor(): UIColor {
		return this._ios.tabBar.barTintColor;
	}

	public setTabBarBackgroundColor(value: UIColor | Color): void {
		this._ios.tabBar.barTintColor = value instanceof Color ? value.ios : value;
		this.updateAllItemsColors();
	}

	public setTabBarItemTitle(tabStripItem: TabStripItem, value: string): void {
		tabStripItem.nativeView.title = value;
	}

	private equalUIColor(first: UIColor, second: UIColor): boolean {
		if (!first && !second) {
			return true;
		}
		if (!first || !second) {
			return false;
		}
		const firstComponents = CGColorGetComponents(first.CGColor);
		const secondComponents = CGColorGetComponents(second.CGColor);

		return firstComponents[0] === secondComponents[0] && firstComponents[1] === secondComponents[1] && firstComponents[2] === secondComponents[2] && firstComponents[3] === secondComponents[3];
	}

	private isSelectedAndHightlightedItem(tabStripItem: TabStripItem): boolean {
		// to find out whether the current tab strip item is active (has style with :active selector applied)
		// we need to check whether its _visualState is equal to "highlighted" as when changing tabs
		// we first go through setTabBarItemBackgroundColor thice, once before setting the "highlighted" state
		// and once after that, but if the "highlighted" state is not set we cannot get the backgroundColor
		// set using :active selector
		return tabStripItem._index === this.selectedIndex && tabStripItem['_visualState'] === 'highlighted';
	}

	public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: UIColor | Color): void {
		if (!this.tabStrip || !tabStripItem) {
			return;
		}

		const newColor = value instanceof Color ? value.ios : value;
		const itemSelectedAndHighlighted = this.isSelectedAndHightlightedItem(tabStripItem);

		// As we cannot implement selected item background color in Tabs we are using the Indicator for this
		// To be able to detect that there are two different background colors (one for selected and one for not selected item)
		// we are checking whether the current item is not selected and higlighted and we store the value of its
		// background color to _defaultItemBackgroundColor and later if we need to process a selected and highlighted item
		// we are comparing it's backgroun color to the default one and if there's a difference
		// we are changing the selectionIndicatorTemplate from underline to the whole item
		// in that mode we are not able to show the indicator as it is used for the background of the selected item

		if (!this._defaultItemBackgroundColor && !itemSelectedAndHighlighted) {
			this._defaultItemBackgroundColor = newColor;
		}

		if (this.viewController.tabBar.alignment !== MDCTabBarAlignment.Justified && itemSelectedAndHighlighted && !this.equalUIColor(this._defaultItemBackgroundColor, newColor)) {
			if (!this._backgroundIndicatorColor) {
				this._backgroundIndicatorColor = newColor;
				this._ios.tabBar.selectionIndicatorTemplate = new BackgroundIndicatorTemplate();
				this._ios.tabBar.tintColor = newColor;
			}
		} else {
			updateBackgroundPositions(this.tabStrip, tabStripItem, newColor);
		}
	}

	public setTabBarItemColor(tabStripItem: TabStripItem, value: UIColor | Color): void {
		this.setViewTextAttributes(tabStripItem.label);
	}

	private setItemColors(): void {
		if (this._selectedItemColor) {
			this.viewController.tabBar.selectedItemTintColor = this._selectedItemColor.ios;
		}
		if (this._unSelectedItemColor) {
			this.viewController.tabBar.unselectedItemTintColor = this._unSelectedItemColor.ios;
		}
	}

	private setIconColor(tabStripItem: TabStripItem, forceReload = false): void {
		// if there is no change in the css color and there is no item color set
		// we don't need to reload the icon
		if (!forceReload && !this._selectedItemColor && !this._unSelectedItemColor) {
			return;
		}

		let image: UIImage;

		// if selectedItemColor or unSelectedItemColor is set we don't respect the color from the style
		const tabStripColor = this.selectedIndex === tabStripItem._index ? this._selectedItemColor : this._unSelectedItemColor;
		image = this.getIcon(tabStripItem, tabStripColor);

		tabStripItem.nativeView.image = image;
	}

	public setTabBarIconColor(tabStripItem: TabStripItem, value: UIColor | Color): void {
		this.setIconColor(tabStripItem, true);
	}

	public setTabBarIconSource(tabStripItem: TabStripItem, value: UIColor | Color): void {
		this.updateItemColors(tabStripItem);
	}

	public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void {
		this.setViewTextAttributes(tabStripItem.label);
	}

	public getTabBarFontInternal(): UIFont {
		return this._ios.tabBar.unselectedItemTitleFont;
	}

	public setTabBarFontInternal(value: Font): void {
		const defaultTabItemFontSize = 10;
		const tabItemFontSize = this.tabStrip.style.fontSize || defaultTabItemFontSize;
		const font: UIFont = (this.tabStrip.style.fontInternal || Font.default).getUIFont(UIFont.systemFontOfSize(tabItemFontSize));

		this._ios.tabBar.unselectedItemTitleFont = font;
		this._ios.tabBar.selectedItemTitleFont = font;
	}

	public getTabBarTextTransform(): TextTransform {
		switch (this._ios.tabBar.titleTextTransform) {
			case MDCTabBarTextTransform.None:
				return 'none';
			case MDCTabBarTextTransform.Automatic:
				return 'initial';
			case MDCTabBarTextTransform.Uppercase:
			default:
				return 'uppercase';
		}
	}

	public setTabBarTextTransform(value: TextTransform): void {
		if (value === 'none') {
			this._ios.tabBar.titleTextTransform = MDCTabBarTextTransform.None;
		} else if (value === 'uppercase') {
			this._ios.tabBar.titleTextTransform = MDCTabBarTextTransform.Uppercase;
		} else if (value === 'initial') {
			this._ios.tabBar.titleTextTransform = MDCTabBarTextTransform.Automatic;
		}
	}

	public getTabBarColor(): UIColor {
		return this._ios.tabBar.titleColorForState(MDCTabBarItemState.Normal);
	}

	public setTabBarColor(value: UIColor | Color): void {
		const nativeColor = value instanceof Color ? value.ios : value;
		this._ios.tabBar.setTitleColorForState(nativeColor, MDCTabBarItemState.Normal);
		this._ios.tabBar.setTitleColorForState(nativeColor, MDCTabBarItemState.Selected);
	}

	public getTabBarHighlightColor(): UIColor {
		return this._ios.tabBar.tintColor;
	}

	public setTabBarHighlightColor(value: UIColor | Color) {
		const nativeColor = value instanceof Color ? value.ios : value;
		this._ios.tabBar.tintColor = nativeColor;
	}

	public getTabBarSelectedItemColor(): Color {
		return this._selectedItemColor;
	}

	public setTabBarSelectedItemColor(value: Color) {
		this._selectedItemColor = value;
		this.updateAllItemsColors();
	}

	public getTabBarUnSelectedItemColor(): Color {
		return this._unSelectedItemColor;
	}

	public setTabBarUnSelectedItemColor(value: Color) {
		this._unSelectedItemColor = value;
		this.updateAllItemsColors();
	}

	private visitFrames(view: ViewBase, operation: (frame: Frame) => {}) {
		if (view instanceof Frame) {
			operation(view);
		}
		view.eachChild((child) => {
			this.visitFrames(child, operation);

			return true;
		});
	}

	[selectedIndexProperty.setNative](value: number) {
		// TODO
		// if (Trace.isEnabled()) {
		//     Trace.write("TabView._onSelectedIndexPropertyChangedSetNativeValue(" + value + ")", Trace.categories.Debug);
		// }

		if (value > -1) {
			const item = this.items[value];
			const controllers = NSMutableArray.alloc<UIViewController>().initWithCapacity(1);

			const itemController = (<any>item).__controller;

			// if (!itemController) {
			//     itemController = this.getViewController(item);
			// }

			controllers.addObject(itemController);

			let navigationDirection = UIPageViewControllerNavigationDirection.Forward;

			if (this._currentNativeSelectedIndex && this._currentNativeSelectedIndex > value) {
				navigationDirection = UIPageViewControllerNavigationDirection.Reverse;
			}

			this._currentNativeSelectedIndex = value;

			// do not make layout changes while the animation is in progress https://stackoverflow.com/a/47031524/613113
			this.visitFrames(item, (frame) => (frame._animationInProgress = true));

			const doneAnimating = () => {
				this.visitFrames(item, (frame) => (frame._animationInProgress = false));

				this._canSelectItem = true;
				this._setCanBeLoaded(value);
				this._loadUnloadTabItems(value);
			};

			invokeOnRunLoop(() =>
				this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, this.animationEnabled, (finished: boolean) => {
					if (finished) {
						if (this.animationEnabled) {
							// HACK: UIPageViewController fix; see https://stackoverflow.com/a/17330606
							// Prior Hack fails on iOS 10.3 during tests with v8 engine...
							// Leaving the above link in case we need to special case this for only iOS > 10.3?

							// HACK: UIPageViewController fix; see https://stackoverflow.com/questions/15325891
							invokeOnRunLoop(() => {
								this.viewController.dataSource = null;
								(<any>this.viewController).dataSource = this.viewController;
								doneAnimating();
							});
						} else {
							doneAnimating();
						}
					}
				})
			);

			if (this.tabBarItems && this.tabBarItems.length && this.viewController && this.viewController.tabBar) {
				this.viewController.tabBar.setSelectedItemAnimated(this.tabBarItems[value], this.animationEnabled);
			}
			// TODO:
			// (<any>this._ios)._willSelectViewController = this._ios.viewControllers[value];
			// this._ios.selectedIndex = value;
		}
	}

	[itemsProperty.getDefault](): TabContentItem[] {
		return null;
	}
	[itemsProperty.setNative](value: TabContentItem[]) {
		if (value) {
			value.forEach((item: TabContentItem, i) => {
				(<any>item).index = i;
			});
		}

		this.setViewControllers(value);
		selectedIndexProperty.coerce(this);
	}

	[tabStripProperty.getDefault](): TabStrip {
		return null;
	}
	[tabStripProperty.setNative](value: TabStrip) {
		this.setViewControllers(this.items);
		selectedIndexProperty.coerce(this);
	}

	[swipeEnabledProperty.getDefault](): boolean {
		return true;
	}
	[swipeEnabledProperty.setNative](value: boolean) {
		if (this.viewController && this.viewController.scrollView) {
			this.viewController.scrollView.scrollEnabled = value;
		}
	}

	[iOSTabBarItemsAlignmentProperty.getDefault](): IOSTabBarItemsAlignment {
		if (!this.viewController || !this.viewController.tabBar) {
			return 'justified';
		}

		const alignment = this.viewController.tabBar.alignment.toString();

		return <any>(alignment.charAt(0).toLowerCase() + alignment.substring(1));
	}
	[iOSTabBarItemsAlignmentProperty.setNative](value: IOSTabBarItemsAlignment) {
		if (!this.viewController || !this.viewController.tabBar) {
			return;
		}

		let alignment = MDCTabBarAlignment.Justified;
		switch (value) {
			case 'leading':
				alignment = MDCTabBarAlignment.Leading;
				break;
			case 'center':
				alignment = MDCTabBarAlignment.Center;
				break;
			case 'centerSelected':
				alignment = MDCTabBarAlignment.CenterSelected;
				break;
		}

		this.viewController.tabBar.alignment = alignment;
	}

	private setViewTextAttributes(view: View, setSelected = false): any {
		if (!view) {
			return null;
		}

		const defaultTabItemFontSize = 10;
		const tabItemFontSize = view.style.fontSize || defaultTabItemFontSize;
		const font: UIFont = (view.style.fontInternal || Font.default).getUIFont(UIFont.systemFontOfSize(tabItemFontSize));

		this.viewController.tabBar.unselectedItemTitleFont = font;
		this.viewController.tabBar.selectedItemTitleFont = font;

		const tabItemTextColor = view.style.color;
		const textColor = tabItemTextColor instanceof Color ? tabItemTextColor.ios : null;

		if (textColor) {
			this.viewController.tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Normal);
			this.viewController.tabBar.setImageTintColorForState(textColor, MDCTabBarItemState.Normal);

			if (setSelected) {
				this.viewController.tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Selected);
				this.viewController.tabBar.setImageTintColorForState(textColor, MDCTabBarItemState.Selected);
			}
		}

		if (this._selectedItemColor) {
			this.viewController.tabBar.selectedItemTintColor = this._selectedItemColor.ios;
		}
		if (this._unSelectedItemColor) {
			this.viewController.tabBar.unselectedItemTintColor = this._unSelectedItemColor.ios;
		}
	}
}
