//
//  NCShareCells.swift
//  Nextcloud
//
//  Created by Henrik Storch on 18.03.22.
//  Copyright © 2022 Henrik Storch. All rights reserved.
//
//  Author Henrik Storch <henrik.storch@nextcloud.com>
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

import UIKit

protocol NCShareCellConfig {
    var title: String { get }
    func getCell(for share: NCTableShareable) -> UITableViewCell
    func didSelect(for share: NCTableShareable)
}

protocol NCToggleCellConfig: NCShareCellConfig {
    func isOn(for share: NCTableShareable) -> Bool
    func didChange(_ share: NCTableShareable, to newValue: Bool)
}

extension NCToggleCellConfig {
    func getCell(for share: NCTableShareable) -> UITableViewCell {
        return NCShareToggleCell(isOn: isOn(for: share))
    }

    func didSelect(for share: NCTableShareable) {
        didChange(share, to: !isOn(for: share))
    }
}

protocol NCPermission: NCToggleCellConfig {
    static var forDirectory: [Self] { get }
    static var forDirectoryE2EE: [Self] { get }
    static var forFile: [Self] { get }
    func hasResharePermission(for parentPermission: Int) -> Bool
    func hasDownload() -> Bool
}

enum NCUserPermission: CaseIterable, NCPermission {
    func hasResharePermission(for parentPermission: Int) -> Bool {
        if self == .download { return true }
        return ((permissionBitFlag & parentPermission) != 0)
    }

    func hasDownload() -> Bool {
        return self == .download
    }

    var permissionBitFlag: Int {
        switch self {
        case .reshare: return NCGlobal.shared.permissionShareShare
        case .edit: return NCGlobal.shared.permissionUpdateShare
        case .create: return NCGlobal.shared.permissionCreateShare
        case .delete: return NCGlobal.shared.permissionDeleteShare
        case .download: return NCGlobal.shared.permissionDownloadShare
        }
    }

    func didChange(_ share: NCTableShareable, to newValue: Bool) {
        if self == .download {
            share.attributes = NCManageDatabase.shared.setAttibuteDownload(state: newValue)
        } else {
            share.permissions ^= permissionBitFlag
        }
    }

    func isOn(for share: NCTableShareable) -> Bool {
        if self == .download {
            return NCManageDatabase.shared.isAttributeDownloadEnabled(attributes: share.attributes)
        } else {
            return (share.permissions & permissionBitFlag) != 0
        }
    }

    case reshare, edit, create, delete, download
    static let forDirectory: [NCUserPermission] = NCUserPermission.allCases
    static let forDirectoryE2EE: [NCUserPermission] = []
    static let forFile: [NCUserPermission] = [.reshare, .edit]

    var title: String {
        switch self {
        case .reshare: return NSLocalizedString("_share_can_reshare_", comment: "")
        case .edit: return NSLocalizedString("_share_can_change_", comment: "")
        case .create: return NSLocalizedString("_share_can_create_", comment: "")
        case .delete: return NSLocalizedString("_share_can_delete_", comment: "")
        case .download: return NSLocalizedString("_share_can_download_", comment: "")
        }
    }
}

enum NCLinkPermission: NCPermission {

    func didChange(_ share: NCTableShareable, to newValue: Bool) {
        guard self != .allowEdit || newValue else {
            share.permissions = NCGlobal.shared.permissionReadShare
            return
        }
        share.permissions = permissionValue
    }

    func hasResharePermission(for parentPermission: Int) -> Bool {
        permissionValue & parentPermission == permissionValue
    }

    func hasDownload() -> Bool {
        return false
    }

    var permissionValue: Int {
        switch self {
        case .allowEdit:
            return CCUtility.getPermissionsValue(
                byCanEdit: true,
                andCanCreate: true,
                andCanChange: true,
                andCanDelete: true,
                andCanShare: false,
                andIsFolder: false)
        case .viewOnly:
            return CCUtility.getPermissionsValue(
                byCanEdit: false,
                andCanCreate: false,
                andCanChange: false,
                andCanDelete: false,
                // not possible to create "read-only" shares without reshare option
                // https://github.com/nextcloud/server/blame/f99876997a9119518fe5f7ad3a3a51d33459d4cc/apps/files_sharing/lib/Controller/ShareAPIController.php#L1104-L1107
                andCanShare: true,
                andIsFolder: true)
        case .uploadEdit:
            return CCUtility.getPermissionsValue(
                byCanEdit: true,
                andCanCreate: true,
                andCanChange: true,
                andCanDelete: true,
                andCanShare: false,
                andIsFolder: true)
        case .fileDrop:
            return NCGlobal.shared.permissionCreateShare
        case .secureFileDrop:
            return NCGlobal.shared.permissionCreateShare
        }
    }

    func isOn(for share: NCTableShareable) -> Bool {
        switch self {
        case .allowEdit: return CCUtility.isAnyPermission(toEdit: share.permissions)
        case .viewOnly: return !CCUtility.isAnyPermission(toEdit: share.permissions) && share.permissions != NCGlobal.shared.permissionCreateShare
        case .uploadEdit: return CCUtility.isAnyPermission(toEdit: share.permissions) && share.permissions != NCGlobal.shared.permissionCreateShare
        case .fileDrop: return share.permissions == NCGlobal.shared.permissionCreateShare
        case .secureFileDrop: return share.permissions == NCGlobal.shared.permissionCreateShare
        }
    }

    var title: String {
        switch self {
        case .allowEdit: return NSLocalizedString("_share_can_change_", comment: "")
        case .viewOnly: return NSLocalizedString("_share_read_only_", comment: "")
        case .uploadEdit: return NSLocalizedString("_share_allow_upload_", comment: "")
        case .fileDrop: return NSLocalizedString("_share_file_drop_", comment: "")
        case .secureFileDrop: return NSLocalizedString("_share_secure_file_drop_", comment: "")
        }
    }

    case allowEdit, viewOnly, uploadEdit, fileDrop, secureFileDrop
    static let forDirectory: [NCLinkPermission] = [.viewOnly, .uploadEdit, .fileDrop]
    static let forFile: [NCLinkPermission] = [.allowEdit]
    static let forDirectoryE2EE: [NCLinkPermission] = [.secureFileDrop]
}

enum NCShareDetails: CaseIterable, NCShareCellConfig {
    func didSelect(for share: NCTableShareable) {
        switch self {
        case .hideDownload: share.hideDownload.toggle()
        case .expirationDate: return
        case .password: return
        case .note: return
        case .label: return
        }
    }

    func getCell(for share: NCTableShareable) -> UITableViewCell {
        switch self {
        case .hideDownload:
            return NCShareToggleCell(isOn: share.hideDownload)
        case .expirationDate:
            return NCShareDateCell(share: share)
        case .password: return NCShareToggleCell(isOn: !share.password.isEmpty, customIcons: ("lock", "lock_open"))
        case .note:
            let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareNote")
            cell.detailTextLabel?.text = share.note
            cell.accessoryType = .disclosureIndicator
            return cell
        case .label:
            let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareLabel")
            cell.detailTextLabel?.text = share.label
            return cell
        }
    }

    var title: String {
        switch self {
        case .hideDownload: return NSLocalizedString("_share_hide_download_", comment: "")
        case .expirationDate: return NSLocalizedString("_share_expiration_date_", comment: "")
        case .password: return NSLocalizedString("_share_password_protect_", comment: "")
        case .note: return NSLocalizedString("_share_note_recipient_", comment: "")
        case .label: return NSLocalizedString("_share_link_name_", comment: "")
        }
    }

    case label, hideDownload, expirationDate, password, note
    static let forLink: [NCShareDetails] = NCShareDetails.allCases
    static let forUser: [NCShareDetails] = [.expirationDate, .note]
}

struct NCShareConfig {
    let permissions: [NCPermission]
    let advanced: [NCShareDetails]
    let share: NCTableShareable
    let resharePermission: Int

    init(parentMetadata: tableMetadata, share: NCTableShareable) {
        self.share = share
        self.resharePermission = parentMetadata.sharePermissionsCollaborationServices
        let type: NCPermission.Type = share.shareType == NCShareCommon.shared.SHARE_TYPE_LINK ? NCLinkPermission.self : NCUserPermission.self
        self.permissions = parentMetadata.directory ? (parentMetadata.e2eEncrypted ? type.forDirectoryE2EE : type.forDirectory) : type.forFile
        self.advanced = share.shareType == NCShareCommon.shared.SHARE_TYPE_LINK ? NCShareDetails.forLink : NCShareDetails.forUser
    }

    func cellFor(indexPath: IndexPath) -> UITableViewCell? {
        let cellConfig = config(for: indexPath)
        let cell = cellConfig?.getCell(for: share)
        cell?.textLabel?.text = cellConfig?.title
        if let cellConfig = cellConfig as? NCPermission, !cellConfig.hasResharePermission(for: resharePermission), !cellConfig.hasDownload() {
            cell?.isUserInteractionEnabled = false
            cell?.textLabel?.isEnabled = false
        }
        return cell
    }

    func didSelectRow(at indexPath: IndexPath) {
        let cellConfig = config(for: indexPath)
        cellConfig?.didSelect(for: share)
    }

    func config(for indexPath: IndexPath) -> NCShareCellConfig? {
        if indexPath.section == 0, indexPath.row < permissions.count {
            return permissions[indexPath.row]
        } else if indexPath.section == 1, indexPath.row < advanced.count {
            return advanced[indexPath.row]
        } else { return nil }
    }
}

class NCShareToggleCell: UITableViewCell {
    typealias CustomToggleIcon = (onIconName: String?, offIconName: String?)
    init(isOn: Bool, customIcons: CustomToggleIcon? = nil) {
        super.init(style: .default, reuseIdentifier: "toggleCell")
        self.accessibilityValue = isOn ? NSLocalizedString("_on_", comment: "") : NSLocalizedString("_off_", comment: "")

        guard let customIcons = customIcons,
              let iconName = isOn ? customIcons.onIconName : customIcons.offIconName else {
            self.accessoryType = isOn ? .checkmark : .none
            return
        }
        let image = NCUtility.shared.loadImage(named: iconName, color: NCBrandColor.shared.brandElement, size: self.frame.height - 26)
        self.accessoryView = UIImageView(image: image)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class NCShareDateCell: UITableViewCell {
    let picker = UIDatePicker()
    let textField = UITextField()
    var shareType: Int
    var onReload: (() -> Void)?

    init(share: NCTableShareable) {
        self.shareType = share.shareType
        super.init(style: .value1, reuseIdentifier: "shareExpDate")

        picker.datePickerMode = .date
        picker.minimumDate = Date()
        picker.preferredDatePickerStyle = .wheels
        picker.action(for: .valueChanged) { datePicker in
            guard let datePicker = datePicker as? UIDatePicker else { return }
            self.detailTextLabel?.text = DateFormatter.shareExpDate.string(from: datePicker.date)
        }
        accessoryView = textField

        let toolbar = UIToolbar.toolbar {
            self.resignFirstResponder()
            share.expirationDate = nil
            self.onReload?()
        } onDone: {
            self.resignFirstResponder()
            share.expirationDate = self.picker.date as NSDate
            self.onReload?()
        }

        textField.isAccessibilityElement = false
        textField.accessibilityElementsHidden = true
        textField.inputAccessoryView = toolbar.wrappedSafeAreaContainer
        textField.inputView = picker

        if let expDate = share.expirationDate {
            detailTextLabel?.text = DateFormatter.shareExpDate.string(from: expDate as Date)
        }
    }

    required public init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func checkMaximumDate(account: String) {
        let defaultExpDays = defaultExpirationDays(account: account)
        if defaultExpDays > 0 && isExpireDateEnforced(account: account) {
            let enforcedInSecs = TimeInterval(defaultExpDays * 24 * 60 * 60)
            self.picker.maximumDate = Date().advanced(by: enforcedInSecs)
        }
    }

    private func isExpireDateEnforced(account: String) -> Bool {
        switch self.shareType {
        case NCShareCommon.shared.SHARE_TYPE_LINK,
            NCShareCommon.shared.SHARE_TYPE_EMAIL,
            NCShareCommon.shared.SHARE_TYPE_GUEST:
            return NCGlobal.shared.capabilityFileSharingPubExpireDateEnforced
        case NCShareCommon.shared.SHARE_TYPE_USER,
            NCShareCommon.shared.SHARE_TYPE_GROUP,
            NCShareCommon.shared.SHARE_TYPE_CIRCLE,
            NCShareCommon.shared.SHARE_TYPE_ROOM:
            return NCGlobal.shared.capabilityFileSharingInternalExpireDateEnforced
        case NCShareCommon.shared.SHARE_TYPE_REMOTE,
            NCShareCommon.shared.SHARE_TYPE_REMOTE_GROUP:
            return NCGlobal.shared.capabilityFileSharingRemoteExpireDateEnforced
        default:
            return false
        }
    }

    private func defaultExpirationDays(account: String) -> Int {
        switch self.shareType {
        case NCShareCommon.shared.SHARE_TYPE_LINK,
            NCShareCommon.shared.SHARE_TYPE_EMAIL,
            NCShareCommon.shared.SHARE_TYPE_GUEST:
            return NCGlobal.shared.capabilityFileSharingPubExpireDateDays
        case NCShareCommon.shared.SHARE_TYPE_USER,
            NCShareCommon.shared.SHARE_TYPE_GROUP,
            NCShareCommon.shared.SHARE_TYPE_CIRCLE,
            NCShareCommon.shared.SHARE_TYPE_ROOM:
            return NCGlobal.shared.capabilityFileSharingInternalExpireDateDays
        case NCShareCommon.shared.SHARE_TYPE_REMOTE,
            NCShareCommon.shared.SHARE_TYPE_REMOTE_GROUP:
            return NCGlobal.shared.capabilityFileSharingRemoteExpireDateDays
        default:
            return 0
        }
    }
}
