/// All Views Binary Tree

/// Bottom View (Idea: Consider axis about root, left side -> -ve and right side -> +ve , use map to store the latest occurrence in that level)

class Solution {
  public:
    vector <int> bottomView(Node *root) {
        vector<int> result;
        if(!root) return result;
        map<int,int> mp;                /// Col, val
        queue<pair<Node*,int>> q;
        q.push({root,0});               /// Queue {Node and Level (-ve , 0, +ve}
        while(!q.empty()){
            int sz=q.size();
            for(int i=0;i<sz;i++){
                Node *temp=q.front().first;
                int level=q.front().second;
                q.pop();
                mp[level]=temp->data;
                if(temp->left) q.push({temp->left,level-1});
                if(temp->right) q.push({temp->right,level+1});
            }
        }
        for(auto it : mp){
            result.push_back(it.second);
        }
    return result;
    }
};

/// Top View (Idea: Consider axis about root, left side -> -ve and right side -> +ve , use map to store the first occurrence in that level by checking its already presence or not)

class Solution
{
    public:
    //Function to return a list of nodes visible from the top view
    //from left to right in Binary Tree.
    vector<int> topView(Node *root)
    {
        vector<int> result;
        if(!root) return result;
        map<int,int> mp;            // col,val
        queue<pair<Node*,int>> q;
        q.push({root,0});
        while(!q.empty()){
            int sz=q.size();
            for(int i=0;i<sz;i++){
                Node *temp=q.front().first;
                int level=q.front().second;
                q.pop();
                if(mp.find(level)==mp.end()) mp[level]=temp->data;
                if(temp->left) q.push({temp->left,level-1});
                if(temp->right) q.push({temp->right,level+1});
            }

        }
        for(auto it : mp){
            result.push_back(it.second);
        }
    return result;
    }

};


/// Vertical Order Traversal (View) // Idea: Maintaining Map (ordered), keep pushing into the level and return into vector afterwards

vector<int> verticalOrder(Node *root)
    {
        vector<int> result;
        if(!root) return result;
        map<int,vector<int>> mp;     // col, vector
        queue<pair<Node*,int>> q;   // Node, level
        q.push({root,0});
        while(!q.empty()){
            int sz=q.size();
            for(int i=0;i<sz;i++){
                Node *temp=q.front().first;
                int level=q.front().second;
                q.pop();
                mp[level].push_back(temp->data);
                if(temp->left) q.push({temp->left,level-1});
                if(temp->right) q.push({temp->right,level+1});
            }
        }
        for(auto it : mp){
            vector<int> temp=it.second;
            for(auto ele : temp){
                result.push_back(ele);
            }
        }
    return result;
    }

    /// Multiset and map based solution (Optimal)

    class Solution {
public:
    vector<vector<int>> verticalTraversal(TreeNode* root) {
        map<int, map<int, multiset<int>>> nodes;
        queue<pair<TreeNode*, pair<int,int>>> q;
        q.push({root, {0,0}});
        while(!q.empty()){
            auto p=q.front();
            q.pop();
            TreeNode* node=p.first;
            int x=p.second.first, y=p.second.second;
            nodes[x][y].insert(node->val);
            if(node->left){
                q.push({node->left, {x-1, y+1}});
            }
             if(node->right){
                q.push({node->right, {x+1, y+1}});
            }
        }
        vector<vector<int>> ans;
        for(auto p: nodes){
            vector<int> col;
            for(auto q: p.second){
                col.insert(col.end(), q.second.begin(), q.second.end());
            }
        ans.push_back(col);
        }
    return ans;
    }
};


/// Left Side View (Idea: recursion, start from root, go to left->left , if not then only go left->right,

class Solution{
public:
    vector<int> leftSideView(TreeNode* root){
        vector<int> ans;
        helper(root, 0, ans);
        return ans;
    }

    void helper(TreeNode* root,int level,vector<int> &ans){
        if(root==NULL)
            return ;
        if(ans.size()==level)
            ans.push_back(root->val);

        helper(root->left, level+1, ans);
        helper(root->right, level+1, ans);
    }
};

/// Left Side View (Queue)

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> res;

        if(!root)
            return res;

        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty())
        {
            int n=q.size();
            for(int i=0;i<n;i++)
            {
                TreeNode * node = q.front();
                q.pop();
                if(node->right)
                    q.push(node->right);
                if(node->left)
                    q.push(node->left);
                if(i==n-1)
                    res.push_back(node->val);
            }
        }
    return res;
    }
};


/// Right Side View (Idea: recursion, start from root, go to right->right , if not then only go right->left,

class Solution {
public:
    void helper(TreeNode *root, int level, vector<int> &result){
        if(!root) return;
        if(result.size()==level){
            result.push_back(root->val);
        }
        helper(root->right,level+1,result);
        helper(root->left,level+1,result);
    }
    vector<int> rightSideView(TreeNode* root) {
        vector<int> result;
        if(!root) return result;
        helper(root,0,result);
    return result;
    }
};

/// Right Side View (Queue)

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> res;

        if(!root)
            return res;

        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty())
        {
            int n=q.size();
            for(int i=0;i<n;i++)
            {
                TreeNode * node = q.front();
                q.pop();
                if(node->left)
                    q.push(node->left);
                if(node->right)
                    q.push(node->right);
                if(i==n-1)
                    res.push_back(node->val);
            }
        }
    return res;
    }
};

/// Boundary View : (Idea : Combination of left side + leaf nodes + right side view)

bool isLeaf(Node *root){
    if(!root->left and !root->right) return true;

return false;
}

void addLeftBoundary(Node *root, vector<int> &result){
    Node *curr=root->left;
    while(curr){
        if(!isLeaf(curr)) result.push_back(curr->data);
        if(curr->left) curr=curr->left;
        else curr=curr->right;
    }
}

void addLeaves(Node *root, vector<int> &result){
    if(isLeaf(root)){
        result.push_back(root->data);
        return;
    }
    if(root->left) addLeaves(root->left,result);
    if(root->right) addLeaves(root->right,result);
}

void addRightBoundary(Node *root, vector<int> &result){
    Node *curr=root->right;
    vector<int> temp;
    while(curr){
        if(!isLeaf(curr)) temp.push_back(curr->data);
        if(curr->right) curr=curr->right;
        else curr=curr->left;
    }
    for(int i=temp.size()-1;i>=0;i--){
        result.push_back(temp[i]);
    }
}

class Solution {
public:
    vector <int> boundary(Node *root)
    {
        vector<int> result;
        if(!root) return result;
        if(!isLeaf(root)) result.push_back(root->data);

        addLeftBoundary(root,result);

        addLeaves(root,result);

        addRightBoundary(root,result);

    return result;
    }
};