/**
 * code generated by JHelper
 * More info: https://github.com/AlexeyDmitriev/JHelper
 * @author Gosu_Hiroo
 */

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
using VI = vector<int>;
using VL = vector<long long>;
using PII = pair<int, int>;
using PLL = pair<long long, long long>;

//#pragma GCC optimize("O3")
//#pragma GCC target("avx2")
//#pragma GCC optimize("unroll-loops")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
//#pragma GCC optimize("Ofast")

#define G(size_1) vector<vector<int>>(size_1, vector<int>())
#define SZ(x) ((long long)(x).size())
#define READ ({long long t;cin >> t;t;})

#define FOR(i, _begin, _end) for (__typeof(_end) end = _end, begin = _begin,  i = (begin) - ((begin) > (end)); i != (end) - ((begin) > (end)); i += 1 - 2 * ((begin) > (end)))
#define REP(i, end) for (__typeof(end) i = 0, _len = (end); i < (_len); i += 1)
#define ALL(x) (x).begin(),(x).end()
#define RALL(x) (x).rbegin(),(x).rend()
#define F first
#define S second
#define y0 y3487465
#define y1 y8687969
#define j0 j1347829
#define j1 j234892
#define BIT(n) (1LL<<(n))
#define UNIQUE(v) v.erase( unique(v.begin(), v.end()), v.end() );
#define EB emplace_back
#define PB push_back
#define fcout cout << fixed << setprecision(12)
#define fcerr cerr << fixed << setprecision(12)
#define print(x) cout << (x) << '\n'
#define printE(x) cout << (x) << '\n';
#define fprint(x) cout << fixed << setprecision(12) << (x) << '\n';
# define BYE(a) do { cout << (a) << endl; return ; } while (false)

#ifdef DEBUG
#define DBG(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); _err(cerr,_it, args); }
#define ERR(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); _err(std::cerr,_it, args); }
#else
#define DBG(args...) {};
#define ERR(args...) {};
#endif

void _err(std::ostream &cerr, istream_iterator<string> it) { cerr << endl; }

template<typename T, typename... Args>
void _err(std::ostream &cerr, istream_iterator<string> it, T a, Args... args) {
    cerr << *it << " = " << a << "  ";
    _err(cerr, ++it, args...);
}

const double PI = 2 * acos(.0);
const int INF = 0x3f3f3f3f;

template<class T>
bool chmax(T &a, const T &b) {
    if (a < b) {
        a = b;
        return 1;
    }
    return 0;
}

template<class T>
bool chmin(T &a, const T &b) {
    if (b < a) {
        a = b;
        return 1;
    }
    return 0;
}

template<class T>
T ceil(T a, T b) { return (a + b - 1) / b; }

template<typename T, typename U>
istream &operator>>(istream &is, pair<T, U> &V) {
    is >> V.F >> V.S;
    return is;
}

template<typename T>
istream &operator>>(istream &is, vector<T> &V) {
    for (auto &&ele : V)is >> ele;
    return is;
}

template<typename T>
ostream &operator<<(ostream &os, const vector<T> V) {
    os << "[";
    int cnt = 0;
    T curr;
    if (!V.empty()) {
        for (int i = 0; i < V.size() - 1; ++i) {
            if (V[i] == curr)cnt++;
            else cnt = 0;
            if (cnt == 4)os << "... ";
            if (cnt < 4)
                os << i << ":" << V[i] << " ";
            curr = V[i];
        }
        os << V.size() - 1 << ":" << V.back();
    }
    os << "]\n";
    return os;
}

template<typename T, typename U>
ostream &operator<<(ostream &os, const pair<T, U> P) {
    os << "(";
    os << P.first << "," << P.second;
    os << ")";
    return os;
}

template<typename T, typename U>
ostream &operator<<(ostream &os, const set<T, U> V) {
    os << "{";
    if (!V.empty()) {
        auto it = V.begin();
        for (int i = 0; i < V.size() - 1; ++i) {
            os << *it << " ";
            it++;
        }
        os << *it;
    }
    os << "}\n";
    return os;
}

template<typename K, typename H, typename P>
ostream &operator<<(ostream &os, const unordered_set<K, H, P> V) {
    os << "{";
    if (!V.empty()) {
        auto it = V.begin();
        for (int i = 0; i < V.size() - 1; ++i) {
            os << *it << " ";
            it++;
        }
        os << *it;
    }
    os << "}\n";
    return os;
}

template<typename K, typename C>
ostream &operator<<(ostream &os, const multiset<K, C> V) {
    os << "{";
    if (!V.empty()) {
        auto it = V.begin();
        for (int i = 0; i < V.size() - 1; ++i) {
            os << *it << " ";
            it++;
        }
        os << *it;
    }
    os << "}";
    return os;
}

template<typename K, typename T, typename C>
ostream &operator<<(ostream &os, const map<K, T, C> V) {
    os << "{";
    if (!V.empty()) {
        auto it = V.begin();
        for (int i = 0; i < V.size() - 1; ++i) {
            os << "(";
            os << it->first << "," << it->second;
            os << ") ";
            it++;
        }
        os << "(";
        os << it->first << "," << it->second;
        os << ")";
    }
    os << "}\n";
    return os;
}

template<typename K, typename T, typename C>
ostream &operator<<(ostream &os, const unordered_map<K, T, C> V) {
    os << "{";
    if (!V.empty()) {
        auto it = V.begin();
        for (int i = 0; i < V.size() - 1; ++i) {
            os << "(";
            os << it->first << "," << it->second;
            os << ") ";
            it++;
        }
        os << "(";
        os << it->first << "," << it->second;
        os << ")";
    }
    os << "}\n";
    return os;
}

template<typename T>
ostream &operator<<(ostream &os, const deque<T> V) {
    os << "[";
    if (!V.empty()) {
        for (int i = 0; i < V.size() - 1; ++i) {
            os << V[i] << "->";
        }
        if (!V.empty())os << V.back();
    }
    os << "]\n";
    return os;
};

template<typename T, typename Cont, typename Comp>
ostream &operator<<(ostream &os, const priority_queue<T, Cont, Comp> V) {
    priority_queue<T, Cont, Comp> _V = V;
    os << "[";
    if (!_V.empty()) {
        while (_V.size() > 1) {
            os << _V.top() << "->";
            _V.pop();
        }
        os << _V.top();
    }
    os << "]\n";
    return os;
};

template<class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template<class... Args>
    decltype(auto) operator()(Args &&... args) const {
        // we pass ourselves to f, then the arguments.
        // the lambda should take the first argument as `auto&& recurse` or similar.
        return f(*this, std::forward<Args>(args)...);
    }
};

// helper function that deduces the type of the lambda:
template<class F>
y_combinator<std::decay_t<F>> recursive(F &&f) {
    return {std::forward<F>(f)};
}

struct hash_pair {
    template<class T1, class T2>
    size_t operator()(const pair<T1, T2> &p) const {
        auto hash1 = hash<T1>{}(p.first);
        auto hash2 = hash<T2>{}(p.second);
        return hash1 ^ hash2;
    }

};

template<typename T, typename U>
std::vector<T> multi_vector(int n, U v) {
    return std::vector<T>(n, v);
}

template<typename U, typename... Args>
auto multi_vector(int n, Args... args) {
    auto val = multi_vector<U>(std::forward<Args>(args)...);
    return std::vector<decltype(val)>(n, std::move(val));
}

#ifndef ATCODER_MAXFLOW_HPP
#define ATCODER_MAXFLOW_HPP 1

#include <algorithm>
#ifndef ATCODER_INTERNAL_QUEUE_HPP
#define ATCODER_INTERNAL_QUEUE_HPP 1

#include <vector>

namespace atcoder {

namespace internal {

template <class T> struct simple_queue {
    std::vector<T> payload;
    int pos = 0;
    void reserve(int n) { payload.reserve(n); }
    int size() const { return int(payload.size()) - pos; }
    bool empty() const { return pos == int(payload.size()); }
    void push(const T& t) { payload.push_back(t); }
    T& front() { return payload[pos]; }
    void clear() {
        payload.clear();
        pos = 0;
    }
    void pop() { pos++; }
};

}  // namespace internal

}  // namespace atcoder

#endif  // ATCODER_INTERNAL_QUEUE_HPP

#include <cassert>
#include <limits>
#include <queue>


namespace atcoder {

template <class Cap> struct mf_graph {
  public:
    mf_graph() : _n(0) {}
    mf_graph(int n) : _n(n), g(n) {}

    int add_edge(int from, int to, Cap cap) {
        assert(0 <= from && from < _n);
        assert(0 <= to && to < _n);
        assert(0 <= cap);
        int m = int(pos.size());
        pos.push_back({from, int(g[from].size())});
        int from_id = int(g[from].size());
        int to_id = int(g[to].size());
        if (from == to) to_id++;
        g[from].push_back(_edge{to, to_id, cap});
        g[to].push_back(_edge{from, from_id, 0});
        return m;
    }

    struct edge {
        int from, to;
        Cap cap, flow;
    };

    edge get_edge(int i) {
        int m = int(pos.size());
        assert(0 <= i && i < m);
        auto _e = g[pos[i].first][pos[i].second];
        auto _re = g[_e.to][_e.rev];
        return edge{pos[i].first, _e.to, _e.cap + _re.cap, _re.cap};
    }
    std::vector<edge> edges() {
        int m = int(pos.size());
        std::vector<edge> result;
        for (int i = 0; i < m; i++) {
            result.push_back(get_edge(i));
        }
        return result;
    }
    void change_edge(int i, Cap new_cap, Cap new_flow) {
        int m = int(pos.size());
        assert(0 <= i && i < m);
        assert(0 <= new_flow && new_flow <= new_cap);
        auto& _e = g[pos[i].first][pos[i].second];
        auto& _re = g[_e.to][_e.rev];
        _e.cap = new_cap - new_flow;
        _re.cap = new_flow;
    }

    Cap flow(int s, int t) {
        return flow(s, t, std::numeric_limits<Cap>::max());
    }
    Cap flow(int s, int t, Cap flow_limit) {
        assert(0 <= s && s < _n);
        assert(0 <= t && t < _n);
        assert(s != t);

        std::vector<int> level(_n), iter(_n);
        internal::simple_queue<int> que;

        auto bfs = [&]() {
            std::fill(level.begin(), level.end(), -1);
            level[s] = 0;
            que.clear();
            que.push(s);
            while (!que.empty()) {
                int v = que.front();
                que.pop();
                for (auto e : g[v]) {
                    if (e.cap == 0 || level[e.to] >= 0) continue;
                    level[e.to] = level[v] + 1;
                    if (e.to == t) return;
                    que.push(e.to);
                }
            }
        };
        auto dfs = [&](auto self, int v, Cap up) {
            if (v == s) return up;
            Cap res = 0;
            int level_v = level[v];
            for (int& i = iter[v]; i < int(g[v].size()); i++) {
                _edge& e = g[v][i];
                if (level_v <= level[e.to] || g[e.to][e.rev].cap == 0) continue;
                Cap d =
                    self(self, e.to, std::min(up - res, g[e.to][e.rev].cap));
                if (d <= 0) continue;
                g[v][i].cap += d;
                g[e.to][e.rev].cap -= d;
                res += d;
                if (res == up) break;
            }
            return res;
        };

        Cap flow = 0;
        while (flow < flow_limit) {
            bfs();
            if (level[t] == -1) break;
            std::fill(iter.begin(), iter.end(), 0);
            while (flow < flow_limit) {
                Cap f = dfs(dfs, t, flow_limit - flow);
                if (!f) break;
                flow += f;
            }
        }
        return flow;
    }

    std::vector<bool> min_cut(int s) {
        std::vector<bool> visited(_n);
        internal::simple_queue<int> que;
        que.push(s);
        while (!que.empty()) {
            int p = que.front();
            que.pop();
            visited[p] = true;
            for (auto e : g[p]) {
                if (e.cap && !visited[e.to]) {
                    visited[e.to] = true;
                    que.push(e.to);
                }
            }
        }
        return visited;
    }

  private:
    int _n;
    struct _edge {
        int to, rev;
        Cap cap;
    };
    std::vector<std::pair<int, int>> pos;
    std::vector<std::vector<_edge>> g;
};

}  // namespace atcoder

#endif  // ATCODER_MAXFLOW_HPP


using namespace atcoder;

class E {
public:

    void solve(std::istream &cin, std::ostream &cout, std::ostream &cerr) {
        int N, M;
        cin >> N >> M;
        vector<string> g(N);
        cin >> g;
        int V = N * M;
        vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};
        auto id = [&](int i, int j) { return i * M + j; };
        auto pos = [&](int x) -> PII { return {x / M, x % M}; };
        auto ok = [&](int i, int j) { return 0 <= i && i < N && 0 <= j && j < M; };
        mf_graph<int> mf_g(2*V + 2);
        REP(i, N)
            REP(j, M) {
                mf_g.add_edge(id(i,j), id(i,j)+V, g[i][j]=='.'?1:INF);
                REP(k, 4) {
                    int n_i = i + dx[k], n_j = j + dy[k];
                    if (ok(n_i, n_j)) {
                        mf_g.add_edge(id(i, j)+V, id(n_i, n_j), 1);
                    }else mf_g.add_edge(id(i,j)+V, 2*V+1, INF);
                }
                if(g[i][j] == 'X')mf_g.add_edge(2*V, id(i,j), INF);
            }
        if(auto ans = mf_g.flow(2*V, 2*V+1);ans > 1e6)print(-1);
        else print(ans);

    }


};


#undef int
int main() {

	E solver;
	std::istream& in(std::cin);
	std::ostream& out(std::cout);
    std::ostringstream err;
	in.tie(0); ios::sync_with_stdio(0);
    solver.solve(in, out,err);
	return 0;
}


Measured memory_bytes: 6892