//  CAPSPageMenu.swift
//
//  Niklas Fahl
//
//  Copyright (c) 2014 The Board of Trustees of The University of Alabama All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//
//  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//
//  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
//
//  Neither the name of the University nor the names of the contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
//  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import UIKit

@objc public protocol CAPSPageMenuDelegate {
  // MARK: - Delegate functions
  
  optional func willMoveToPage(controller: UIViewController, index: Int)
  optional func didMoveToPage(controller: UIViewController, index: Int)
}

class MenuItemView: UIView {
  // MARK: - Menu item view
  
  var titleLabel : UILabel?
  var menuItemSeparator : UIView?
  
  func setUpMenuItemView(menuItemWidth: CGFloat, menuScrollViewHeight: CGFloat, indicatorHeight: CGFloat, separatorPercentageHeight: CGFloat, separatorWidth: CGFloat, separatorRoundEdges: Bool, menuItemSeparatorColor: UIColor) {
    titleLabel = UILabel(frame: CGRectMake(0.0, 0.0, menuItemWidth, menuScrollViewHeight - indicatorHeight))
    
    menuItemSeparator = UIView(frame: CGRectMake(menuItemWidth - (separatorWidth / 2), floor(menuScrollViewHeight * ((1.0 - separatorPercentageHeight) / 2.0)), separatorWidth, floor(menuScrollViewHeight * separatorPercentageHeight)))
    menuItemSeparator!.backgroundColor = menuItemSeparatorColor
    
    if separatorRoundEdges {
      menuItemSeparator!.layer.cornerRadius = menuItemSeparator!.frame.width / 2
    }
    
    menuItemSeparator!.hidden = true
    self.addSubview(menuItemSeparator!)
    
    self.addSubview(titleLabel!)
  }
  
  func setTitleText(text: NSString) {
    if titleLabel != nil {
      titleLabel!.text = text as String
      titleLabel!.numberOfLines = 0
      titleLabel!.sizeToFit()
    }
  }
}

public enum CAPSPageMenuOption {
  case SelectionIndicatorHeight(CGFloat)
  case MenuItemSeparatorWidth(CGFloat)
  case ScrollMenuBackgroundColor(UIColor)
  case ViewBackgroundColor(UIColor)
  case BottomMenuHairlineColor(UIColor)
  case SelectionIndicatorColor(UIColor)
  case MenuItemSeparatorColor(UIColor)
  case MenuMargin(CGFloat)
  case MenuItemMargin(CGFloat)
  case MenuHeight(CGFloat)
  case SelectedMenuItemLabelColor(UIColor)
  case UnselectedMenuItemLabelColor(UIColor)
  case UseMenuLikeSegmentedControl(Bool)
  case MenuItemSeparatorRoundEdges(Bool)
  case MenuItemFont(UIFont)
  case MenuItemSeparatorPercentageHeight(CGFloat)
  case MenuItemWidth(CGFloat)
  case EnableHorizontalBounce(Bool)
  case AddBottomMenuHairline(Bool)
  case MenuItemWidthBasedOnTitleTextWidth(Bool)
  case TitleTextSizeBasedOnMenuItemWidth(Bool)
  case ScrollAnimationDurationOnMenuItemTap(Int)
  case CenterMenuItems(Bool)
  case HideTopMenuBar(Bool)
}

public class CAPSPageMenu: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
  
  // MARK: - Properties
  
  let menuScrollView = UIScrollView()
  let controllerScrollView = UIScrollView()
  var controllerArray : [UIViewController] = []
  var menuItems : [MenuItemView] = []
  var menuItemWidths : [CGFloat] = []
  
  public var menuHeight : CGFloat = 34.0
  public var menuMargin : CGFloat = 15.0
  public var menuItemWidth : CGFloat = 111.0
  public var selectionIndicatorHeight : CGFloat = 3.0
  var totalMenuItemWidthIfDifferentWidths : CGFloat = 0.0
  public var scrollAnimationDurationOnMenuItemTap : Int = 500 // Millisecons
  var startingMenuMargin : CGFloat = 0.0
  var menuItemMargin : CGFloat = 0.0
  
  var selectionIndicatorView : UIView = UIView()
  
  var currentPageIndex : Int = 0
  var lastPageIndex : Int = 0
  
  public var selectionIndicatorColor : UIColor = UIColor.whiteColor()
  public var selectedMenuItemLabelColor : UIColor = UIColor.whiteColor()
  public var unselectedMenuItemLabelColor : UIColor = UIColor.lightGrayColor()
  public var scrollMenuBackgroundColor : UIColor = UIColor.blackColor()
  public var viewBackgroundColor : UIColor = UIColor.whiteColor()
  public var bottomMenuHairlineColor : UIColor = UIColor.whiteColor()
  public var menuItemSeparatorColor : UIColor = UIColor.lightGrayColor()
  
  public var menuItemFont : UIFont = UIFont.systemFontOfSize(15.0)
  public var menuItemSeparatorPercentageHeight : CGFloat = 0.2
  public var menuItemSeparatorWidth : CGFloat = 0.5
  public var menuItemSeparatorRoundEdges : Bool = false
  
  public var addBottomMenuHairline : Bool = true
  public var menuItemWidthBasedOnTitleTextWidth : Bool = false
  public var titleTextSizeBasedOnMenuItemWidth : Bool = false
  public var useMenuLikeSegmentedControl : Bool = false
  public var centerMenuItems : Bool = false
  public var enableHorizontalBounce : Bool = true
  public var hideTopMenuBar : Bool = false
  
  var currentOrientationIsPortrait : Bool = true
  var pageIndexForOrientationChange : Int = 0
  var didLayoutSubviewsAfterRotation : Bool = false
  var didScrollAlready : Bool = false
  
  var lastControllerScrollViewContentOffset : CGFloat = 0.0
  
  var lastScrollDirection : CAPSPageMenuScrollDirection = .Other
  var startingPageForScroll : Int = 0
  var didTapMenuItemToScroll : Bool = false
  
  var pagesAddedDictionary : [Int : Int] = [:]
  
  public weak var delegate : CAPSPageMenuDelegate?
  
  var tapTimer : NSTimer?
  
  enum CAPSPageMenuScrollDirection : Int {
    case Left
    case Right
    case Other
  }
  
  // MARK: - View life cycle
  
  /**
   Initialize PageMenu with view controllers
   
   :param: viewControllers List of view controllers that must be subclasses of UIViewController
   :param: frame Frame for page menu view
   :param: options Dictionary holding any customization options user might want to set
   */
  public init(viewControllers: [UIViewController], frame: CGRect, options: [String: AnyObject]?) {
    super.init(nibName: nil, bundle: nil)
    
    controllerArray = viewControllers
    
    self.view.frame = frame
  }
  
  public convenience init(viewControllers: [UIViewController], frame: CGRect, pageMenuOptions: [CAPSPageMenuOption]?) {
    self.init(viewControllers:viewControllers, frame:frame, options:nil)
    
    if let options = pageMenuOptions {
      for option in options {
        switch (option) {
        case let .SelectionIndicatorHeight(value):
          selectionIndicatorHeight = value
        case let .MenuItemSeparatorWidth(value):
          menuItemSeparatorWidth = value
        case let .ScrollMenuBackgroundColor(value):
          scrollMenuBackgroundColor = value
        case let .ViewBackgroundColor(value):
          viewBackgroundColor = value
        case let .BottomMenuHairlineColor(value):
          bottomMenuHairlineColor = value
        case let .SelectionIndicatorColor(value):
          selectionIndicatorColor = value
        case let .MenuItemSeparatorColor(value):
          menuItemSeparatorColor = value
        case let .MenuMargin(value):
          menuMargin = value
        case let .MenuItemMargin(value):
          menuItemMargin = value
        case let .MenuHeight(value):
          menuHeight = value
        case let .SelectedMenuItemLabelColor(value):
          selectedMenuItemLabelColor = value
        case let .UnselectedMenuItemLabelColor(value):
          unselectedMenuItemLabelColor = value
        case let .UseMenuLikeSegmentedControl(value):
          useMenuLikeSegmentedControl = value
        case let .MenuItemSeparatorRoundEdges(value):
          menuItemSeparatorRoundEdges = value
        case let .MenuItemFont(value):
          menuItemFont = value
        case let .MenuItemSeparatorPercentageHeight(value):
          menuItemSeparatorPercentageHeight = value
        case let .MenuItemWidth(value):
          menuItemWidth = value
        case let .EnableHorizontalBounce(value):
          enableHorizontalBounce = value
        case let .AddBottomMenuHairline(value):
          addBottomMenuHairline = value
        case let .MenuItemWidthBasedOnTitleTextWidth(value):
          menuItemWidthBasedOnTitleTextWidth = value
        case let .TitleTextSizeBasedOnMenuItemWidth(value):
          titleTextSizeBasedOnMenuItemWidth = value
        case let .ScrollAnimationDurationOnMenuItemTap(value):
          scrollAnimationDurationOnMenuItemTap = value
        case let .CenterMenuItems(value):
          centerMenuItems = value
        case let .HideTopMenuBar(value):
          hideTopMenuBar = value
        }
      }
      
      if hideTopMenuBar {
        addBottomMenuHairline = false
        menuHeight = 0.0
      }
    }
    
    setUpUserInterface()
    
    if menuScrollView.subviews.count == 0 {
      configureUserInterface()
    }
  }
  
  required public init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }
  
  // MARK: - Container View Controller
  public override func shouldAutomaticallyForwardAppearanceMethods() -> Bool {
    return true
  }
  
  public override func shouldAutomaticallyForwardRotationMethods() -> Bool {
    return true
  }
  
  // MARK: - UI Setup
  
  func setUpUserInterface() {
    let viewsDictionary = ["menuScrollView":menuScrollView, "controllerScrollView":controllerScrollView]
    
    // Set up controller scroll view
    controllerScrollView.pagingEnabled = true
    controllerScrollView.translatesAutoresizingMaskIntoConstraints = false
    controllerScrollView.alwaysBounceHorizontal = enableHorizontalBounce
    controllerScrollView.bounces = enableHorizontalBounce
    
    controllerScrollView.frame = CGRectMake(0.0, menuHeight, self.view.frame.width, self.view.frame.height)
    
    self.view.addSubview(controllerScrollView)
    
    let controllerScrollView_constraint_H:Array = NSLayoutConstraint.constraintsWithVisualFormat("H:|[controllerScrollView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
    let controllerScrollView_constraint_V:Array = NSLayoutConstraint.constraintsWithVisualFormat("V:|[controllerScrollView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
    
    self.view.addConstraints(controllerScrollView_constraint_H)
    self.view.addConstraints(controllerScrollView_constraint_V)
    
    // Set up menu scroll view
    menuScrollView.translatesAutoresizingMaskIntoConstraints = false
    
    menuScrollView.frame = CGRectMake(0.0, 0.0, self.view.frame.width, menuHeight)
    
    self.view.addSubview(menuScrollView)
    
    let menuScrollView_constraint_H:Array = NSLayoutConstraint.constraintsWithVisualFormat("H:|[menuScrollView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
    let menuScrollView_constraint_V:Array = NSLayoutConstraint.constraintsWithVisualFormat("V:[menuScrollView(\(menuHeight))]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
    
    self.view.addConstraints(menuScrollView_constraint_H)
    self.view.addConstraints(menuScrollView_constraint_V)
    
    // Add hairline to menu scroll view
    if addBottomMenuHairline {
      let menuBottomHairline : UIView = UIView()
      
      menuBottomHairline.translatesAutoresizingMaskIntoConstraints = false
      
      self.view.addSubview(menuBottomHairline)
      
      let menuBottomHairline_constraint_H:Array = NSLayoutConstraint.constraintsWithVisualFormat("H:|[menuBottomHairline]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["menuBottomHairline":menuBottomHairline])
      let menuBottomHairline_constraint_V:Array = NSLayoutConstraint.constraintsWithVisualFormat("V:|-\(menuHeight)-[menuBottomHairline(0.5)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["menuBottomHairline":menuBottomHairline])
      
      self.view.addConstraints(menuBottomHairline_constraint_H)
      self.view.addConstraints(menuBottomHairline_constraint_V)
      
      menuBottomHairline.backgroundColor = bottomMenuHairlineColor
    }
    
    // Disable scroll bars
    menuScrollView.showsHorizontalScrollIndicator = false
    menuScrollView.showsVerticalScrollIndicator = false
    controllerScrollView.showsHorizontalScrollIndicator = false
    controllerScrollView.showsVerticalScrollIndicator = false
    
    // Set background color behind scroll views and for menu scroll view
    self.view.backgroundColor = viewBackgroundColor
    menuScrollView.backgroundColor = scrollMenuBackgroundColor
  }
  
  func configureUserInterface() {
    // Add tap gesture recognizer to controller scroll view to recognize menu item selection
    let menuItemTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(CAPSPageMenu.handleMenuItemTap(_:)))
    menuItemTapGestureRecognizer.numberOfTapsRequired = 1
    menuItemTapGestureRecognizer.numberOfTouchesRequired = 1
    menuItemTapGestureRecognizer.delegate = self
    menuScrollView.addGestureRecognizer(menuItemTapGestureRecognizer)
    
    // Set delegate for controller scroll view
    controllerScrollView.delegate = self
    
    // When the user taps the status bar, the scroll view beneath the touch which is closest to the status bar will be scrolled to top,
    // but only if its `scrollsToTop` property is YES, its delegate does not return NO from `shouldScrollViewScrollToTop`, and it is not already at the top.
    // If more than one scroll view is found, none will be scrolled.
    // Disable scrollsToTop for menu and controller scroll views so that iOS finds scroll views within our pages on status bar tap gesture.
    menuScrollView.scrollsToTop = false;
    controllerScrollView.scrollsToTop = false;
    
    // Configure menu scroll view
    if useMenuLikeSegmentedControl {
      menuScrollView.scrollEnabled = false
      menuScrollView.contentSize = CGSizeMake(self.view.frame.width, menuHeight)
      menuMargin = 0.0
    } else {
      menuScrollView.contentSize = CGSizeMake((menuItemWidth + menuMargin) * CGFloat(controllerArray.count) + menuMargin, menuHeight)
    }
    
    // Configure controller scroll view content size
    controllerScrollView.contentSize = CGSizeMake(self.view.frame.width * CGFloat(controllerArray.count), 0.0)
    
    var index : CGFloat = 0.0
    
    for controller in controllerArray {
      if index == 0.0 {
        // Add first two controllers to scrollview and as child view controller
        addPageAtIndex(0)
      }
      
      // Set up menu item for menu scroll view
      var menuItemFrame : CGRect = CGRect()
      
      if useMenuLikeSegmentedControl {
        //**************************拡張*************************************
        if menuItemMargin > 0 {
          let marginSum = menuItemMargin * CGFloat(controllerArray.count + 1)
          let menuItemWidth = (self.view.frame.width - marginSum) / CGFloat(controllerArray.count)
          menuItemFrame = CGRectMake(CGFloat(menuItemMargin * (index + 1)) + menuItemWidth * CGFloat(index), 0.0, CGFloat(self.view.frame.width) / CGFloat(controllerArray.count), menuHeight)
        } else {
          menuItemFrame = CGRectMake(self.view.frame.width / CGFloat(controllerArray.count) * CGFloat(index), 0.0, CGFloat(self.view.frame.width) / CGFloat(controllerArray.count), menuHeight)
        }
        //**************************拡張ここまで*************************************
      } else if menuItemWidthBasedOnTitleTextWidth {
        let controllerTitle : String? = controller.title
        
        let titleText : String = controllerTitle != nil ? controllerTitle! : "Menu \(Int(index) + 1)"
        
        let itemWidthRect : CGRect = (titleText as NSString).boundingRectWithSize(CGSizeMake(1000, 1000), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName:menuItemFont], context: nil)
        
        menuItemWidth = itemWidthRect.width
        
        menuItemFrame = CGRectMake(totalMenuItemWidthIfDifferentWidths + menuMargin + (menuMargin * index), 0.0, menuItemWidth, menuHeight)
        
        totalMenuItemWidthIfDifferentWidths += itemWidthRect.width
        menuItemWidths.append(itemWidthRect.width)
      } else {
        if centerMenuItems && index == 0.0  {
          startingMenuMargin = ((self.view.frame.width - ((CGFloat(controllerArray.count) * menuItemWidth) + (CGFloat(controllerArray.count - 1) * menuMargin))) / 2.0) -  menuMargin
          
          if startingMenuMargin < 0.0 {
            startingMenuMargin = 0.0
          }
          
          menuItemFrame = CGRectMake(startingMenuMargin + menuMargin, 0.0, menuItemWidth, menuHeight)
        } else {
          menuItemFrame = CGRectMake(menuItemWidth * index + menuMargin * (index + 1) + startingMenuMargin, 0.0, menuItemWidth, menuHeight)
        }
      }
      
      let menuItemView : MenuItemView = MenuItemView(frame: menuItemFrame)
      if useMenuLikeSegmentedControl {
        //**************************拡張*************************************
        if menuItemMargin > 0 {
          let marginSum = menuItemMargin * CGFloat(controllerArray.count + 1)
          let menuItemWidth = (self.view.frame.width - marginSum) / CGFloat(controllerArray.count)
          menuItemView.setUpMenuItemView(menuItemWidth, menuScrollViewHeight: menuHeight, indicatorHeight: selectionIndicatorHeight, separatorPercentageHeight: menuItemSeparatorPercentageHeight, separatorWidth: menuItemSeparatorWidth, separatorRoundEdges: menuItemSeparatorRoundEdges, menuItemSeparatorColor: menuItemSeparatorColor)
        } else {
          menuItemView.setUpMenuItemView(CGFloat(self.view.frame.width) / CGFloat(controllerArray.count), menuScrollViewHeight: menuHeight, indicatorHeight: selectionIndicatorHeight, separatorPercentageHeight: menuItemSeparatorPercentageHeight, separatorWidth: menuItemSeparatorWidth, separatorRoundEdges: menuItemSeparatorRoundEdges, menuItemSeparatorColor: menuItemSeparatorColor)
        }
        //**************************拡張ここまで*************************************
      } else {
        menuItemView.setUpMenuItemView(menuItemWidth, menuScrollViewHeight: menuHeight, indicatorHeight: selectionIndicatorHeight, separatorPercentageHeight: menuItemSeparatorPercentageHeight, separatorWidth: menuItemSeparatorWidth, separatorRoundEdges: menuItemSeparatorRoundEdges, menuItemSeparatorColor: menuItemSeparatorColor)
      }
      
      // Configure menu item label font if font is set by user
      menuItemView.titleLabel!.font = menuItemFont
      
      menuItemView.titleLabel!.textAlignment = NSTextAlignment.Center
      menuItemView.titleLabel!.textColor = unselectedMenuItemLabelColor
      
      //**************************拡張*************************************
      menuItemView.titleLabel!.adjustsFontSizeToFitWidth = titleTextSizeBasedOnMenuItemWidth
      //**************************拡張ここまで*************************************
      
      // Set title depending on if controller has a title set
      if controller.title != nil {
        menuItemView.titleLabel!.text = controller.title!
      } else {
        menuItemView.titleLabel!.text = "Menu \(Int(index) + 1)"
      }
      
      // Add separator between menu items when using as segmented control
      if useMenuLikeSegmentedControl {
        if Int(index) < controllerArray.count - 1 {
          menuItemView.menuItemSeparator!.hidden = false
        }
      }
      
      // Add menu item view to menu scroll view
      menuScrollView.addSubview(menuItemView)
      menuItems.append(menuItemView)
      
      index++
    }
    
    // Set new content size for menu scroll view if needed
    if menuItemWidthBasedOnTitleTextWidth {
      menuScrollView.contentSize = CGSizeMake((totalMenuItemWidthIfDifferentWidths + menuMargin) + CGFloat(controllerArray.count) * menuMargin, menuHeight)
    }
    
    // Set selected color for title label of selected menu item
    if menuItems.count > 0 {
      if menuItems[currentPageIndex].titleLabel != nil {
        menuItems[currentPageIndex].titleLabel!.textColor = selectedMenuItemLabelColor
      }
    }
    
    // Configure selection indicator view
    var selectionIndicatorFrame : CGRect = CGRect()
    
    if useMenuLikeSegmentedControl {
      selectionIndicatorFrame = CGRectMake(0.0, menuHeight - selectionIndicatorHeight, self.view.frame.width / CGFloat(controllerArray.count), selectionIndicatorHeight)
    } else if menuItemWidthBasedOnTitleTextWidth {
      selectionIndicatorFrame = CGRectMake(menuMargin, menuHeight - selectionIndicatorHeight, menuItemWidths[0], selectionIndicatorHeight)
    } else {
      if centerMenuItems  {
        selectionIndicatorFrame = CGRectMake(startingMenuMargin + menuMargin, menuHeight - selectionIndicatorHeight, menuItemWidth, selectionIndicatorHeight)
      } else {
        selectionIndicatorFrame = CGRectMake(menuMargin, menuHeight - selectionIndicatorHeight, menuItemWidth, selectionIndicatorHeight)
      }
    }
    
    selectionIndicatorView = UIView(frame: selectionIndicatorFrame)
    selectionIndicatorView.backgroundColor = selectionIndicatorColor
    menuScrollView.addSubview(selectionIndicatorView)
    
    if menuItemWidthBasedOnTitleTextWidth && centerMenuItems {
      self.configureMenuItemWidthBasedOnTitleTextWidthAndCenterMenuItems()
      let leadingAndTrailingMargin = self.getMarginForMenuItemWidthBasedOnTitleTextWidthAndCenterMenuItems()
      selectionIndicatorView.frame = CGRectMake(leadingAndTrailingMargin, menuHeight - selectionIndicatorHeight, menuItemWidths[0], selectionIndicatorHeight)
    }
  }
  
  // Adjusts the menu item frames to size item width based on title text width and center all menu items in the center
  // if the menuItems all fit in the width of the view. Otherwise, it will adjust the frames so that the menu items
  // appear as if only menuItemWidthBasedOnTitleTextWidth is true.
  private func configureMenuItemWidthBasedOnTitleTextWidthAndCenterMenuItems() {
    // only center items if the combined width is less than the width of the entire view's bounds
    if menuScrollView.contentSize.width < CGRectGetWidth(self.view.bounds) {
      // compute the margin required to center the menu items
      let leadingAndTrailingMargin = self.getMarginForMenuItemWidthBasedOnTitleTextWidthAndCenterMenuItems()
      
      // adjust the margin of each menu item to make them centered
      for (index, menuItem) in menuItems.enumerate() {
        let controllerTitle = controllerArray[index].title!
        
        let itemWidthRect = controllerTitle.boundingRectWithSize(CGSizeMake(1000, 1000), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName:menuItemFont], context: nil)
        
        menuItemWidth = itemWidthRect.width
        
        var margin: CGFloat
        if index == 0 {
          // the first menu item should use the calculated margin
          margin = leadingAndTrailingMargin
        } else {
          // the other menu items should use the menuMargin
          let previousMenuItem = menuItems[index-1]
          let previousX = CGRectGetMaxX(previousMenuItem.frame)
          margin = previousX + menuMargin
        }
        
        menuItem.frame = CGRectMake(margin, 0.0, menuItemWidth, menuHeight)
      }
    } else {
      // the menuScrollView.contentSize.width exceeds the view's width, so layout the menu items normally (menuItemWidthBasedOnTitleTextWidth)
      for (index, menuItem) in menuItems.enumerate() {
        var menuItemX: CGFloat
        if index == 0 {
          menuItemX = menuMargin
        } else {
          menuItemX = CGRectGetMaxX(menuItems[index-1].frame) + menuMargin
        }
        
        menuItem.frame = CGRectMake(menuItemX, 0.0, CGRectGetWidth(menuItem.bounds), CGRectGetHeight(menuItem.bounds))
      }
    }
  }
  
  // Returns the size of the left and right margins that are neccessary to layout the menuItems in the center.
  private func getMarginForMenuItemWidthBasedOnTitleTextWidthAndCenterMenuItems() -> CGFloat {
    let menuItemsTotalWidth = menuScrollView.contentSize.width - menuMargin * 2
    let leadingAndTrailingMargin = (CGRectGetWidth(self.view.bounds) - menuItemsTotalWidth) / 2
    
    return leadingAndTrailingMargin
  }
  
  
  // MARK: - Scroll view delegate
  
  public func scrollViewDidScroll(scrollView: UIScrollView) {
    if !didLayoutSubviewsAfterRotation {
      if scrollView.isEqual(controllerScrollView) {
        if scrollView.contentOffset.x >= 0.0 && scrollView.contentOffset.x <= (CGFloat(controllerArray.count - 1) * self.view.frame.width) {
          if (currentOrientationIsPortrait && UIApplication.sharedApplication().statusBarOrientation.isPortrait) || (!currentOrientationIsPortrait && UIApplication.sharedApplication().statusBarOrientation.isLandscape) {
            // Check if scroll direction changed
            if !didTapMenuItemToScroll {
              if didScrollAlready {
                var newScrollDirection : CAPSPageMenuScrollDirection = .Other
                
                if (CGFloat(startingPageForScroll) * scrollView.frame.width > scrollView.contentOffset.x) {
                  newScrollDirection = .Right
                } else if (CGFloat(startingPageForScroll) * scrollView.frame.width < scrollView.contentOffset.x) {
                  newScrollDirection = .Left
                }
                
                if newScrollDirection != .Other {
                  if lastScrollDirection != newScrollDirection {
                    let index : Int = newScrollDirection == .Left ? currentPageIndex + 1 : currentPageIndex - 1
                    
                    if index >= 0 && index < controllerArray.count {
                      // Check dictionary if page was already added
                      if pagesAddedDictionary[index] != index {
                        addPageAtIndex(index)
                        pagesAddedDictionary[index] = index
                      }
                    }
                  }
                }
                
                lastScrollDirection = newScrollDirection
              }
              
              if !didScrollAlready {
                if (lastControllerScrollViewContentOffset > scrollView.contentOffset.x) {
                  if currentPageIndex != controllerArray.count - 1 {
                    // Add page to the left of current page
                    let index : Int = currentPageIndex - 1
                    
                    if pagesAddedDictionary[index] != index && index < controllerArray.count && index >= 0 {
                      addPageAtIndex(index)
                      pagesAddedDictionary[index] = index
                    }
                    
                    lastScrollDirection = .Right
                  }
                } else if (lastControllerScrollViewContentOffset < scrollView.contentOffset.x) {
                  if currentPageIndex != 0 {
                    // Add page to the right of current page
                    let index : Int = currentPageIndex + 1
                    
                    if pagesAddedDictionary[index] != index && index < controllerArray.count && index >= 0 {
                      addPageAtIndex(index)
                      pagesAddedDictionary[index] = index
                    }
                    
                    lastScrollDirection = .Left
                  }
                }
                
                didScrollAlready = true
              }
              
              lastControllerScrollViewContentOffset = scrollView.contentOffset.x
            }
            
            var ratio : CGFloat = 1.0
            
            
            // Calculate ratio between scroll views
            ratio = (menuScrollView.contentSize.width - self.view.frame.width) / (controllerScrollView.contentSize.width - self.view.frame.width)
            
            if menuScrollView.contentSize.width > self.view.frame.width {
              var offset : CGPoint = menuScrollView.contentOffset
              offset.x = controllerScrollView.contentOffset.x * ratio
              menuScrollView.setContentOffset(offset, animated: false)
            }
            
            // Calculate current page
            let width : CGFloat = controllerScrollView.frame.size.width;
            let page : Int = Int((controllerScrollView.contentOffset.x + (0.5 * width)) / width)
            
            // Update page if changed
            if page != currentPageIndex {
              lastPageIndex = currentPageIndex
              currentPageIndex = page
              
              if pagesAddedDictionary[page] != page && page < controllerArray.count && page >= 0 {
                addPageAtIndex(page)
                pagesAddedDictionary[page] = page
              }
              
              if !didTapMenuItemToScroll {
                // Add last page to pages dictionary to make sure it gets removed after scrolling
                if pagesAddedDictionary[lastPageIndex] != lastPageIndex {
                  pagesAddedDictionary[lastPageIndex] = lastPageIndex
                }
                
                // Make sure only up to 3 page views are in memory when fast scrolling, otherwise there should only be one in memory
                let indexLeftTwo : Int = page - 2
                if pagesAddedDictionary[indexLeftTwo] == indexLeftTwo {
                  pagesAddedDictionary.removeValueForKey(indexLeftTwo)
                  removePageAtIndex(indexLeftTwo)
                }
                let indexRightTwo : Int = page + 2
                if pagesAddedDictionary[indexRightTwo] == indexRightTwo {
                  pagesAddedDictionary.removeValueForKey(indexRightTwo)
                  removePageAtIndex(indexRightTwo)
                }
              }
            }
            
            // Move selection indicator view when swiping
            moveSelectionIndicator(page)
          }
        } else {
          var ratio : CGFloat = 1.0
          
          ratio = (menuScrollView.contentSize.width - self.view.frame.width) / (controllerScrollView.contentSize.width - self.view.frame.width)
          
          if menuScrollView.contentSize.width > self.view.frame.width {
            var offset : CGPoint = menuScrollView.contentOffset
            offset.x = controllerScrollView.contentOffset.x * ratio
            menuScrollView.setContentOffset(offset, animated: false)
          }
        }
      }
    } else {
      didLayoutSubviewsAfterRotation = false
      
      // Move selection indicator view when swiping
      moveSelectionIndicator(currentPageIndex)
    }
  }
  
  public func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    if scrollView.isEqual(controllerScrollView) {
      // Call didMoveToPage delegate function
      let currentController = controllerArray[currentPageIndex]
      delegate?.didMoveToPage?(currentController, index: currentPageIndex)
      
      // Remove all but current page after decelerating
      for key in pagesAddedDictionary.keys {
        if key != currentPageIndex {
          removePageAtIndex(key)
        }
      }
      
      didScrollAlready = false
      startingPageForScroll = currentPageIndex
      
      
      // Empty out pages in dictionary
      pagesAddedDictionary.removeAll(keepCapacity: false)
    }
  }
  
  func scrollViewDidEndTapScrollingAnimation() {
    // Call didMoveToPage delegate function
    let currentController = controllerArray[currentPageIndex]
    delegate?.didMoveToPage?(currentController, index: currentPageIndex)
    
    // Remove all but current page after decelerating
    for key in pagesAddedDictionary.keys {
      if key != currentPageIndex {
        removePageAtIndex(key)
      }
    }
    
    startingPageForScroll = currentPageIndex
    didTapMenuItemToScroll = false
    
    // Empty out pages in dictionary
    pagesAddedDictionary.removeAll(keepCapacity: false)
  }
  
  
  // MARK: - Handle Selection Indicator
  func moveSelectionIndicator(pageIndex: Int) {
    if pageIndex >= 0 && pageIndex < controllerArray.count {
      UIView.animateWithDuration(0.15, animations: { () -> Void in
        var selectionIndicatorWidth : CGFloat = self.selectionIndicatorView.frame.width
        var selectionIndicatorX : CGFloat = 0.0
        
        if self.useMenuLikeSegmentedControl {
          selectionIndicatorX = CGFloat(pageIndex) * (self.view.frame.width / CGFloat(self.controllerArray.count))
          selectionIndicatorWidth = self.view.frame.width / CGFloat(self.controllerArray.count)
        } else if self.menuItemWidthBasedOnTitleTextWidth {
          selectionIndicatorWidth = self.menuItemWidths[pageIndex]
          selectionIndicatorX = CGRectGetMinX(self.menuItems[pageIndex].frame)
        } else {
          if self.centerMenuItems && pageIndex == 0 {
            selectionIndicatorX = self.startingMenuMargin + self.menuMargin
          } else {
            selectionIndicatorX = self.menuItemWidth * CGFloat(pageIndex) + self.menuMargin * CGFloat(pageIndex + 1) + self.startingMenuMargin
          }
        }
        
        self.selectionIndicatorView.frame = CGRectMake(selectionIndicatorX, self.selectionIndicatorView.frame.origin.y, selectionIndicatorWidth, self.selectionIndicatorView.frame.height)
        
        // Switch newly selected menu item title label to selected color and old one to unselected color
        if self.menuItems.count > 0 {
          if self.menuItems[self.lastPageIndex].titleLabel != nil && self.menuItems[self.currentPageIndex].titleLabel != nil {
            self.menuItems[self.lastPageIndex].titleLabel!.textColor = self.unselectedMenuItemLabelColor
            self.menuItems[self.currentPageIndex].titleLabel!.textColor = self.selectedMenuItemLabelColor
          }
        }
      })
    }
  }
  
  
  // MARK: - Tap gesture recognizer selector
  
  func handleMenuItemTap(gestureRecognizer : UITapGestureRecognizer) {
    let tappedPoint : CGPoint = gestureRecognizer.locationInView(menuScrollView)
    
    if tappedPoint.y < menuScrollView.frame.height {
      
      // Calculate tapped page
      var itemIndex : Int = 0
      
      if useMenuLikeSegmentedControl {
        itemIndex = Int(tappedPoint.x / (self.view.frame.width / CGFloat(controllerArray.count)))
      } else if menuItemWidthBasedOnTitleTextWidth {
        var menuItemLeftBound: CGFloat
        var menuItemRightBound: CGFloat
        
        if centerMenuItems {
          menuItemLeftBound = CGRectGetMinX(menuItems[0].frame)
          menuItemRightBound = CGRectGetMaxX(menuItems[menuItems.count-1].frame)
          
          if (tappedPoint.x >= menuItemLeftBound && tappedPoint.x <= menuItemRightBound) {
            for (index, _) in controllerArray.enumerate() {
              menuItemLeftBound = CGRectGetMinX(menuItems[index].frame)
              menuItemRightBound = CGRectGetMaxX(menuItems[index].frame)
              
              if tappedPoint.x >= menuItemLeftBound && tappedPoint.x <= menuItemRightBound {
                itemIndex = index
                break
              }
            }
          }
        } else {
          // Base case being first item
          menuItemLeftBound = 0.0
          menuItemRightBound = menuItemWidths[0] + menuMargin + (menuMargin / 2)
          
          if !(tappedPoint.x >= menuItemLeftBound && tappedPoint.x <= menuItemRightBound) {
            for i in 1...controllerArray.count - 1 {
              menuItemLeftBound = menuItemRightBound + 1.0
              menuItemRightBound = menuItemLeftBound + menuItemWidths[i] + menuMargin
              
              if tappedPoint.x >= menuItemLeftBound && tappedPoint.x <= menuItemRightBound {
                itemIndex = i
                break
              }
            }
          }
        }
      } else {
        let rawItemIndex : CGFloat = ((tappedPoint.x - startingMenuMargin) - menuMargin / 2) / (menuMargin + menuItemWidth)
        
        // Prevent moving to first item when tapping left to first item
        if rawItemIndex < 0 {
          itemIndex = -1
        } else {
          itemIndex = Int(rawItemIndex)
        }
      }
      
      if itemIndex >= 0 && itemIndex < controllerArray.count {
        // Update page if changed
        if itemIndex != currentPageIndex {
          startingPageForScroll = itemIndex
          lastPageIndex = currentPageIndex
          currentPageIndex = itemIndex
          didTapMenuItemToScroll = true
          
          // Add pages in between current and tapped page if necessary
          let smallerIndex : Int = lastPageIndex < currentPageIndex ? lastPageIndex : currentPageIndex
          let largerIndex : Int = lastPageIndex > currentPageIndex ? lastPageIndex : currentPageIndex
          
          if smallerIndex + 1 != largerIndex {
            for index in (smallerIndex + 1)...(largerIndex - 1) {
              if pagesAddedDictionary[index] != index {
                addPageAtIndex(index)
                pagesAddedDictionary[index] = index
              }
            }
          }
          
          addPageAtIndex(itemIndex)
          
          // Add page from which tap is initiated so it can be removed after tap is done
          pagesAddedDictionary[lastPageIndex] = lastPageIndex
        }
        
        // Move controller scroll view when tapping menu item
        let duration : Double = Double(scrollAnimationDurationOnMenuItemTap) / Double(1000)
        
        UIView.animateWithDuration(duration, animations: { () -> Void in
          let xOffset : CGFloat = CGFloat(itemIndex) * self.controllerScrollView.frame.width
          self.controllerScrollView.setContentOffset(CGPoint(x: xOffset, y: self.controllerScrollView.contentOffset.y), animated: false)
        })
        
        if tapTimer != nil {
          tapTimer!.invalidate()
        }
        
        let timerInterval : NSTimeInterval = Double(scrollAnimationDurationOnMenuItemTap) * 0.001
        tapTimer = NSTimer.scheduledTimerWithTimeInterval(timerInterval, target: self, selector: #selector(CAPSPageMenu.scrollViewDidEndTapScrollingAnimation), userInfo: nil, repeats: false)
      }
    }
  }
  
  
  // MARK: - Remove/Add Page
  func addPageAtIndex(index : Int) {
    // Call didMoveToPage delegate function
    let currentController = controllerArray[index]
    delegate?.willMoveToPage?(currentController, index: index)
    
    let newVC = controllerArray[index]
    
    newVC.willMoveToParentViewController(self)
    
    newVC.view.frame = CGRectMake(self.view.frame.width * CGFloat(index), menuHeight, self.view.frame.width, self.view.frame.height - menuHeight)
    
    self.addChildViewController(newVC)
    self.controllerScrollView.addSubview(newVC.view)
    newVC.didMoveToParentViewController(self)
  }
  
  func removePageAtIndex(index : Int) {
    let oldVC = controllerArray[index]
    
    oldVC.willMoveToParentViewController(nil)
    
    oldVC.view.removeFromSuperview()
    oldVC.removeFromParentViewController()
  }
  
  
  // MARK: - Orientation Change
  
  override public func viewDidLayoutSubviews() {
    // Configure controller scroll view content size
    controllerScrollView.contentSize = CGSizeMake(self.view.frame.width * CGFloat(controllerArray.count), self.view.frame.height - menuHeight)
    
    let oldCurrentOrientationIsPortrait : Bool = currentOrientationIsPortrait
    currentOrientationIsPortrait = UIApplication.sharedApplication().statusBarOrientation.isPortrait
    
    if (oldCurrentOrientationIsPortrait && UIDevice.currentDevice().orientation.isLandscape) || (!oldCurrentOrientationIsPortrait && UIDevice.currentDevice().orientation.isPortrait) {
      didLayoutSubviewsAfterRotation = true
      
      //Resize menu items if using as segmented control
      if useMenuLikeSegmentedControl {
        menuScrollView.contentSize = CGSizeMake(self.view.frame.width, menuHeight)
        
        // Resize selectionIndicator bar
        let selectionIndicatorX : CGFloat = CGFloat(currentPageIndex) * (self.view.frame.width / CGFloat(self.controllerArray.count))
        let selectionIndicatorWidth : CGFloat = self.view.frame.width / CGFloat(self.controllerArray.count)
        selectionIndicatorView.frame =  CGRectMake(selectionIndicatorX, self.selectionIndicatorView.frame.origin.y, selectionIndicatorWidth, self.selectionIndicatorView.frame.height)
        
        // Resize menu items
        var index : Int = 0
        
        for item : MenuItemView in menuItems as [MenuItemView] {
          item.frame = CGRectMake(self.view.frame.width / CGFloat(controllerArray.count) * CGFloat(index), 0.0, self.view.frame.width / CGFloat(controllerArray.count), menuHeight)
          item.titleLabel!.frame = CGRectMake(0.0, 0.0, self.view.frame.width / CGFloat(controllerArray.count), menuHeight)
          item.menuItemSeparator!.frame = CGRectMake(item.frame.width - (menuItemSeparatorWidth / 2), item.menuItemSeparator!.frame.origin.y, item.menuItemSeparator!.frame.width, item.menuItemSeparator!.frame.height)
          
          index += 1
        }
      } else if menuItemWidthBasedOnTitleTextWidth && centerMenuItems {
        self.configureMenuItemWidthBasedOnTitleTextWidthAndCenterMenuItems()
        let selectionIndicatorX = CGRectGetMinX(menuItems[currentPageIndex].frame)
        selectionIndicatorView.frame = CGRectMake(selectionIndicatorX, menuHeight - selectionIndicatorHeight, menuItemWidths[currentPageIndex], selectionIndicatorHeight)
      } else if centerMenuItems {
        startingMenuMargin = ((self.view.frame.width - ((CGFloat(controllerArray.count) * menuItemWidth) + (CGFloat(controllerArray.count - 1) * menuMargin))) / 2.0) -  menuMargin
        
        if startingMenuMargin < 0.0 {
          startingMenuMargin = 0.0
        }
        
        let selectionIndicatorX : CGFloat = self.menuItemWidth * CGFloat(currentPageIndex) + self.menuMargin * CGFloat(currentPageIndex + 1) + self.startingMenuMargin
        selectionIndicatorView.frame =  CGRectMake(selectionIndicatorX, self.selectionIndicatorView.frame.origin.y, self.selectionIndicatorView.frame.width, self.selectionIndicatorView.frame.height)
        
        // Recalculate frame for menu items if centered
        var index : Int = 0
        
        for item : MenuItemView in menuItems as [MenuItemView] {
          if index == 0 {
            item.frame = CGRectMake(startingMenuMargin + menuMargin, 0.0, menuItemWidth, menuHeight)
          } else {
            item.frame = CGRectMake(menuItemWidth * CGFloat(index) + menuMargin * CGFloat(index + 1) + startingMenuMargin, 0.0, menuItemWidth, menuHeight)
          }
          
          index += 1
        }
      }
      
      for view : UIView in controllerScrollView.subviews {
        view.frame = CGRectMake(self.view.frame.width * CGFloat(currentPageIndex), menuHeight, controllerScrollView.frame.width, self.view.frame.height - menuHeight)
      }
      
      let xOffset : CGFloat = CGFloat(self.currentPageIndex) * controllerScrollView.frame.width
      controllerScrollView.setContentOffset(CGPoint(x: xOffset, y: controllerScrollView.contentOffset.y), animated: false)
      
      let ratio : CGFloat = (menuScrollView.contentSize.width - self.view.frame.width) / (controllerScrollView.contentSize.width - self.view.frame.width)
      
      if menuScrollView.contentSize.width > self.view.frame.width {
        var offset : CGPoint = menuScrollView.contentOffset
        offset.x = controllerScrollView.contentOffset.x * ratio
        menuScrollView.setContentOffset(offset, animated: false)
      }
    }
    
    // Hsoi 2015-02-05 - Running on iOS 7.1 complained: "'NSInternalInconsistencyException', reason: 'Auto Layout
    // still required after sending -viewDidLayoutSubviews to the view controller. ViewController's implementation
    // needs to send -layoutSubviews to the view to invoke auto layout.'"
    //
    // http://stackoverflow.com/questions/15490140/auto-layout-error
    //
    // Given the SO answer and caveats presented there, we'll call layoutIfNeeded() instead.
    self.view.layoutIfNeeded()
  }
  
  
  // MARK: - Move to page index
  
  /**
   Move to page at index
   
   :param: index Index of the page to move to
   */
  public func moveToPage(index: Int) {
    if index >= 0 && index < controllerArray.count {
      // Update page if changed
      if index != currentPageIndex {
        startingPageForScroll = index
        lastPageIndex = currentPageIndex
        currentPageIndex = index
        didTapMenuItemToScroll = true
        
        // Add pages in between current and tapped page if necessary
        let smallerIndex : Int = lastPageIndex < currentPageIndex ? lastPageIndex : currentPageIndex
        let largerIndex : Int = lastPageIndex > currentPageIndex ? lastPageIndex : currentPageIndex
        
        if smallerIndex + 1 != largerIndex {
          for i in (smallerIndex + 1)...(largerIndex - 1) {
            if pagesAddedDictionary[i] != i {
              addPageAtIndex(i)
              pagesAddedDictionary[i] = i
            }
          }
        }
        
        addPageAtIndex(index)
        
        // Add page from which tap is initiated so it can be removed after tap is done
        pagesAddedDictionary[lastPageIndex] = lastPageIndex
      }
      
      // Move controller scroll view when tapping menu item
      let duration : Double = Double(scrollAnimationDurationOnMenuItemTap) / Double(1000)
      
      UIView.animateWithDuration(duration, animations: { () -> Void in
        let xOffset : CGFloat = CGFloat(index) * self.controllerScrollView.frame.width
        self.controllerScrollView.setContentOffset(CGPoint(x: xOffset, y: self.controllerScrollView.contentOffset.y), animated: false)
      })
    }
  }
}
